Loading partial view with an Edit Form doesn't overload correctly - forms

I am trying to load an edit form which is a PartialView, into a div using jQuery. I am overloading the EditUser action. The 1st one is for passing the id and loading the form with existing details. The 2nd one is for posting back the form for save. But it seems to call the 2nd method when I load using jQuery and Url.Action. If I comment out the 2nd EditUser method, then it calls the 1st method. Why is that? How can I make it call the 1st one when I pass staffID? Or is there a better way to implement this Edit form in partial view scenario??
And the CreateUser action works just fine as there is no ambiguity on the overloaded methods as 1 has no parameters and the other has a model as parameter.
Thanks
This is my controller:
public PartialViewResult EditUser(String staffId)
{
User um = userService.GetUserDetails(1, staffId, true);
return PartialView(um);
}
[HttpPost]
public PartialViewResult EditUser(User um)
{
if (!TryUpdateModel(um))
{
ViewBag.updateError = "Edit Failure";
return PartialView("EditUser", um);
}
userService.CreateUpdateUser(um);
return PartialView("ViewUser", um);
}
public PartialViewResult CreateUser()
{
ViewBag.Message = "Create New User";
return PartialView(new User());
}
[HttpPost]
public ActionResult CreateUser(User um)
{
if (!TryUpdateModel(um))
{
ViewBag.updateError = "Create Failure";
return PartialView(um);
}
userService.CreateUpdateUser(um);
return View("Index");
}
This is how I am loading my EditUser partialview:
function menuEdit() {
$('#ActionMenu').hide();
$('#SearchBar').hide();
$('#SearchPanel').hide();
$('#SearchResult').hide();
$('#AddViewEditUser').load("#Url.Action("EditUser","User")", {staffId : sId});
$('#AddViewEditUser').show();
}

According to jQuery .load() "The POST method is used if data is provided as an object; otherwise, GET is assumed." Since you are providing data, .load() is using the method "POST", thus your second EditUser() is being called.

Related

ZEND: Displaying form error messages on failed validation

I have a form say:
class Application_Form_UserDetails extends Zend_Form
{
public function init()
{
$pswd = new Zend_Form_Element_Password('password');
$pswd->setLabel('New password:');
$pswd->setAttrib('size', 25);
$pswd->setRequired(false);
$pswd->addValidator('StringLength', false, array(4,15));
$pswd->addErrorMessage('Wron password');
}
}
In my user details controller class I have:
class UserDetailsController extends Zend_Controller_Action {
public function editAction()
{
$userId = $this->userInfo->id;
$DbTableUsers = new Application_Model_DbTable_User;
$obj = $DbTableUsers->getUserDetails($userId);
$this->view->formUser = new $this->_UserDetails_form_class;
$this->view->formCompany = new $this->_CompanyDetails_form_class;
if ($obj) {
$this->view->formUser->populate($obj);
}
$url = $this->view->url(array('action' => 'update-user-details'));
$this->view->formUser->setAction($url);
}
public function updateUserDetailsAction()
{
$formUser = new $this->_UserDetails_form_class;
if ($formUser->isValid($this->getRequest()->getPost())) {
}
else {
//validation failed
$formUser->markAsError();
$this->view->formUser = $formUser;
$this->_helper->redirector('edit', 'user-details');
}
}
}
The first time Edit action is called the form built and displayed.
User fills the form and sends it (updateUserDetailsAction is called).
In updateUserDetailsAction, on validation failure I mark the form as having errors and want to display the form with error messages that I previously set in updateUserDetailsAction class.
Then I redirect:
$this->_helper->redirector('edit', 'user-details');
in order to display the same form but with errors for the user to re-enter correct values.
The problem is I don't know how to let know the edit action that the form must display validation errors?
On $this->_helper->redirector('edit', 'user-details'); the form is redisplayed
as a new form with cleared erros but I need them displayed.
Do I do this the correct way?
regards
Tom
Problem comes from the fact that you are redirecting and in each method you are creating a new instance of the form, that means the form class is loosing its state - data you injected from the request and any other values passed to this object.
Combine editAction and updateUserDetailsAction into one method:
...
$formUser = new Form();
// populate the form from the model
if ($this->getRequest()->isPost()) {
if ($formUser->isValid($this->getRequest()->getPost())) {
// update the model
}
}
...
and have the form being submitted to the edit action. This will simplify your code and remove code duplication.
If you just wan to fix your code you can instantiate the form object in the init() method of your controller as set it as a property of your controller. This will way you will reuse same instance after redirection. I still think that solution above is much more compact and easier to understand for someone else.

Facebook c# sdk mvc3 canvas issue with CanvasAuthorize

I'm creating a mvc3 canvas app using facebook c# sdk
The method name is create.
I also do a post and have another create method with [HttpPost] attribute.
When I add the [CanvasAuthorize(Permissions = ExtendedPermissions)] attribute to both the create methods, and a link from another page calls this create method, normally the get method should get called but in this case the post method gets called
But if I comment the post method then it goes to the get method.
Any ideas how to solve this.
Thanks
Arnab
This is because of the canvas authorization posting the access token into the page. The only way around it I've found is to create a different action that deals the post and use that action inside the view as post target. It will look something like this:
// /MyController/MyAction
// Post and Get
[CanvasAuthorize(Permissions = ExtendedPermissions]
public ActionResult MyAction(MyModel data)
{
MyModel modelData = data;
if(data==null)
{
modelData = new MyModel();
}
else
{
modelData = data;
}
return View(modelData);
}
// /MyController/MyActionPost
// POST only
[HttpPost]
[CanvasAuthorize(Permissions = ExtendedPermissions]
public ActionResult MyActionPost(MyModel data)
{
if(Model.IsValid)
{
//Processing code with a redirect at the end (most likely)
}
else
{
return View("MyAction", data);
}
}
Then in your MyAction view:
#using (Html.BeginForm("MyActionPost", "MyController"))
{
<!-- Form items go here-->
<inpuy type="submit" value="Submit" />
#Html.FacebookSignedRequest()
}
I have the same issue. It was doing a GET before, then suddenly when browse to an action with [CanvasAuthorize(Permissions = ExtendedPermissions)] attribute, it's doing a POST instead of a GET.

How do I configure Fubu for a view without a controller?

I have an Index action on a controller that's not doing anything.
public EmptyModel Index()
{
return null;
}
The Index view simply displays some html, with jQuery-driven ajax and the MasterPage doing all the heavy lifting on this particular page. When I remove this action function from it's controller, the aspx view will no longer display.
More Information and Update:
After making the changes mentioned in Chad's answer the url that used to return the index view now instead returns a 404. This issue may exist because most of the views' folder structure is done in the early Fubu Framework style (with View_Page_Type_Declarations.cs and no code-behinds), rather than using the more intuitive and more recent default folder conventions. But it's possible my analysis is off.
Here's my FubuRegistry:
public WebAppFubuRegistry()
{
IncludeDiagnostics(true);
Services(x => x.SetServiceIfNone<IWebAppSecurityContext, WebAppSecurityContext>());
Applies.ToThisAssembly()
.ToAssemblyContainingType<HomeController>();
Actions
.IncludeClassesSuffixedWithController();
Routes
.UrlPolicy<WebAppUrlPolicy>()
.IgnoreControllerNamespaceEntirely()
.ConstrainToHttpMethod(action => action.Method.Name.StartsWith("Perform"), "POST");
Views
.TryToAttach(x=> x.by<ViewAndActionInDifferentFolders>())
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
/*Behavior Code */
}
WebAppUrlPolicy:
public class WebAppUrlPolicy : IUrlPolicy
{
public bool Matches(ActionCall call, IConfigurationObserver log)
{
return true;
}
public IRouteDefinition Build(ActionCall call)
{
if(call.IsForHomeController())
return new RouteDefinition("home");
if(call.IsAnIndexCall())
return new RouteDefinition(call.ControllerPrefix());
var otherRoute = new RouteDefinition(call.ToControllerActionRoute());
return otherRoute;
}
}
ViewAndActionInDifferentFolders:
public class ViewAndActionInDifferentFolders : IViewsForActionFilter
{
public IEnumerable<IViewToken> Apply(ActionCall call, ViewBag views)
{
if (call.IsForHomeController())
{
var viewTokens = views.ViewsFor(call.OutputType()).Where(x => x.Name == "HomeIndexView");
return new[] { new WebAppViewToken(call, viewTokens, "home") };
}
if (call.IsJsonCall())
{
return new List<IViewToken>();
}
return CreateSingleTokenList(call, views);
}
private static IEnumerable<WebAppViewToken> CreateSingleTokenList(ActionCall call, ViewBag views)
{
return new[] { new WebAppViewToken(call, views.ViewsFor(call.OutputType())) };
}
}
How do I reconfigure Fubu so that I can use a view without the action?
What changes need to be made to remove the action function above, and still maintain the same functionality?
In your FubuRegistry, in the "Views" section, add:
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView, chain => chain.PartialOnly());
For example, the whole views section may look like:
Views
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(
WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
Note that you can both ASPX and ASCX for headless views. If you only want ASCX files, then you can use WebFormViewFacility.IsWebFormControl instead.
Works for me:
Views.RegisterActionLessViews(type => type.Name == "StaticView",
chain => chain.Route = new RouteDefinition("StaticView"));

MVC DropDownListFor - Must I manually re-populate the options after validation fail?

I have a viewmodel class which contains a couple of properties. Basically, the current record (which the user is editing) and a list of options (which is used to populate a dropdown list using DropDownListFor).
After the form is submitted, if the modelstate is not valid I return to the view. I understand that the form is populated using the 'rejected' input from ModelState["name"].Value.AttemptedValue, but I'm not sure what to do about the list of values for the dropdown list.
If I do nothing, on the validation fail and return to the page I get an 'object reference not set to instance of an object' error because the list property of the viewmodel is null. I know that it's null because it wasn't bound from the form post, so I can repopulate it from the database before returning to the view.
Is that the correct way to go about it, or am I missing a more obvious way of making the dropdown values persist?
Yes, that's the correct way if you intend to return the same view in the POST action:
bind the list in the GET action from database
render the view
the user submits the form to the POST action
in the this action you fetch only the selected value so if the model is invalid and you need to redisplay the view you need to get the list back from the database in order to populate your view model.
Here's an example of a commonly used pattern in MVC:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Items = _repository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
// Validation failed, fetch the list from the database
// and redisplay the form
model.Items = _repository.GetItems();
return View(model);
}
// at this stage the model is valid =>
// use the selected value from the dropdown
_repository.DoSomething(model.SelectedValue);
// You no longer need to fetch the list because
// we are redirecting here
return RedirectToAction("Success", "SomeOtherController");
}
}
You can use xhr ajax call to submit your data, instead of submitting the form by it's default submit button.
The advantage of this technique is you will not need to repopulate your lists.
On the client side and after ajax call back you can decide to do what ever you want by check on the status value
$.ajax({
url: '#Url.Action("Index", "Controller")',
data: $("#form").serialize(),
type: 'POST',
success: function (data) {
if (data.status == "Successful") {
// you can redirect to another page by window.location.replace('New URL')
} else {
// you can display validation message
}
}
});
You ActionMethod will be like:
[HttpPost]
public JsonResult Index()
{
if (ModelState.IsValid)
{
return Json(new { status = "Success" });
}
else
{
return Json(new { status = "Fail" });
}
}

How do I pass data between two controllers?

In my specific example, I need to pass an error received on one controller to another controller where it will be display. Here is a test case I set up. I've tried TempData, ViewData and Session. One other thing I noticed is that maybe it's the way I'm redirecting. When I put a breakpoint on the receiving controller if I just go to it I hit the breakpoint, but on the redirect it never hits.
Sending Controller Action
public ActionResult New()
{
Session["Notice"] = "There was an error";
Session["NoticeClass"] = "error";
return RedirectToAction("Index", "Home");
}
Then here's the receiving controller:
public ActionResult Index()
{
//Handle action
return View();
}
Then a partial view renders out any errors or notices found
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%
string Message = "";
string Class = "hidden";
if (ViewData["Notice"] != null && ViewData["Notice"] != "")
{
Message = (string)ViewData["Notice"];
Class = (string)ViewData["NoticeClass"];
}
if (Session["Notice"] != null && Session["Notice"] != "")
{
Message = (string)Session["Notice"];
Class = (string)Session["NoticeClass"];
Session["Notice"] = null;
}
Response.Write("<div class=\"" + Class + "\" id=\"error_div\"><span id=\"error_span\">" + Message + "</span></div>");
%>
UPDATE : Firstly, Sorry but i still
cant get a clear picture - assuming
you want to get the data in one
controller action pass it to another
controller's action and then render
this in a partial view. You can use
Sessions to get the values on the
other controller just in a way you
stored it....but tempdata i think might also work in your case..then for redirection -
return RedirectToAction("Action","Controller",routevalues)
I think you should read about tempdata
and viewdata more here and dont
use ViewData unless you have assigned
it some value which I can't see in your
code and you are still using it.
Tempdata stores value per request....so a new request means it will lose values.Have a look at this if you are looking to pass values using tempdata.
So, in your case if you are only looking to pass a string do something like this -
public ActionResult New()
{
string str = "There was an error";
return RedirectToAction("Index",str);
}
public ActionResult Index(string str)
{
Response.Write(str);
return View();
}
So apparently there's something specific about redirecting to the root of the site. When I changed the redirect away from /home/index to another action it worked fine. It was only when I redirected to that one that my values disappeared.