Unexpected form url with mixed aspx & controller MVC .Net routes - model-view-controller

I have two routes defined thus:
//Custom route for legacy admin page
routes.MapPageRoute(
"LocaliseRoute", // Route name
"Admin/Localise", // URL
"~/Views/Admin/Localise.aspx" // File
);
routes.MapRoute(
"Admin", // Route name
"Admin/{action}/{id}", // URL with parameters
new { controller = "Admin", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
both the following GETs work fine:
http://pegfect.local/Admin/PegModelUpload
http://pegfect.local/Admin/Localise
However, the form action of the former is /Admin/Localise?action=UploadPegModel&controller=Admin
resulting in an expression of "WTF?!"
the code for the form is:
#using (Html.BeginForm("UploadPegModel", "Admin", FormMethod.Post, new { enctype = "multipart/form-data", onsubmit = "return validateForm();" }))
{
<input type='file' name='file' id='file' />
<input type="submit" value="submit" />
}

The answer is right here: http://bartwullems.blogspot.co.uk/2011/04/combining-aspnet-webforms-and-aspnet.html
Answers on a postcard as to why this would work...

Related

#Ajax.ActionLink - passing value of text-area to controller, and a data-something attribute

This is the story:
I am making a commenting system, and when a user wants to add a comment they need to put data in a text area. I want to take that value typed by the user and make an #Ajax link which is to send that as a parameter to a controller.
I am using ASP.NET MVC5, and in my View() I have the following:
<textarea class="textArea" rows="3"></textarea>
<br />
#Ajax.ActionLink("Send",
"AddComment",
new { parametar = 0 , Contents = GetText() },
new AjaxOptions
{
UpdateTargetId = "beforeThis",
InsertionMode = InsertionMode.InsertBefore,
HttpMethod = "GET"
},
new { #class = "postavi btn btn-primary" })
I tried inserting under this the following:
<script type="text/javascript">
function GetText() {
return "hello there!";
}
</script>
I have in error saying that:
the name GetText does not exists in the current Context
(this is in the parameters of the #Ajax.ActionLink)
It seems I cannot integrate javascript (which could fetch me this value and razor code) How do I work this out???
PS> I have searched around for this, and either the answers for much earlier versions of MVC or the answers did not worked when I tried the same.
Make sure that you import this namespace:
using System.Web.Mvc.Ajax
You might add an event handler to the ajax link to update a custom route value.
#Ajax.ActionLink("Click", "Send", new {id = "xxx"}, new AjaxOptions(){}, new { onclick = "addParameter(this)" })
function addParameter(e) {
e.href = e.href.replace("xxx", "HelloWord");
}
What you are doing now is that you want the razor to call your JavaScript code and this is impossible. This is because Views will be rendered to HTML by Razor before they are sent to the client and Razor doesn't know about the JavaScript code, it only knows C#. All JavaScript code runs on the browser.
I suggest you use the POST method to send your comments.
You can use this code to send them:
#using (Ajax.BeginForm("AddComment", new { parametar = 0 }, new AjaxOptions()
{
UpdateTargetId = "beforeThis",
InsertionMode = InsertionMode.InsertBefore,
HttpMethod = "POST",
Url = Url.Action("AddComment")
}))
{
#Html.TextArea("Contents")
<input type="submit" value="Send" class="postavi btn btn-primary" />
}

how to include AntiForgeryToken in an ajax action link in mvc?

I have the following code:
#Ajax.ActionLink("Delete", "Delete",
new { id = item.ID, RequestVerificationToken=*What comes here?*},
new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "formsIndex" })
I want to add the verification token to the link without using javascript in client side, it seems like a redundant dependancy since i already own that value in server. Is there a proper way to do that?
From the MSDN documentation (my emphasis)
HtmlHelper.AntiForgeryToken Method
Generates a hidden form field (anti-forgery token) that is validated when the form is submitted.
You need a form element to generate the anti-forgery token.
#Ajax.BeginForm("Delete", new { id = item.ID }, new AjaxOptions { UpdateTargetId = "formsIndex" }))
{
#Html.AntiForgeryToken()
<input type="submit" value="Delete" /> // style to look like a link if that's what you want
}

How to display ValidationSummary using multiple forms using MVC

I have looked at many of the solutions on offer on this site and others for my problem but none of them seem to work perfectly for my solution.
On my Layout page I have a login area on the top of the screen. This is always present unless a user is logged in. This login form has a ValidationSummary on it and every time I post back using another form on the site the validation for this login form is being triggered.
I'm almost certain that this is down to how I call this login page from my Layout page. It is not a partial view that is in a Shared folder, it is in an Area in my project. On my Layout page I call the login form like this:
#Html.Action("LogOn", "Account", new { area = "Store" })
The logon page contains the following:
#model Project.ViewModels.LogOn_ViewModel
#{
Layout = null;
}
#using (Ajax.BeginForm("LogOn", "Account", new { #area = "Store" }, new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "LoginContainer", LoadingElementId = "actionLoaderImage" }, new { id="LogonForm" }))
{
#Html.AntiForgeryToken()
<div class="loginArea">
<div class="notRegistered">
<h4>Not registered yet?</h4>
Register now<br/>
#Html.ActionLink("Register", "Index", "SignUp", new { area = "Store" }, new { #class = "greenButton" })
</div> <!-- /notRegistered -->
<div class="loginForm">
<div class="formFields">
<label class="inline">#Html.LabelFor(m => m.UserName)</label>
#Html.TextBoxFor(m => m.UserName)
<label class="inline">#Html.LabelFor(m => m.Password)</label>
#Html.PasswordFor(m => m.Password)
<input type="submit" name="LogIn" value="Log in" class="blueButton" />
<img id="actionLoaderImage" src="#Url.Content("~/Content/web/images/loader.gif")" alt="icon" style="margin-right:15px; display:none;" />
#Html.ValidationSummary()
</div>
</div> <!-- /loginForm -->
</div> <!-- /loginArea -->
}
The login controller is standard stuff:
// GET: /Account/Logon
public ActionResult LogOn()
{
// if logged in show logged in view
if (User.Identity.IsAuthenticated)
return View("LoggedIn");
return View();
}
// POST: /Account/Logon
[HttpPost]
public ActionResult LogOn(LogOn_ViewModel model)
{
if (ModelState.IsValid)
{
if (SecurityService.Login(model.UserName, model.Password, model.RememberMe))
{
return View("LoggedIn");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return PartialView(model);
}
I suspect that what is happening here is that Html.Action is 'posting' the login form if a post is occurring elsewhere on the page. This makes sense as the layout page itself would be posted as part of a form post action.
I tried implementing the custom Validator examples from some other SO questions and blogs (http://blogs.imeta.co.uk/MBest/archive/2010/01/19/833.aspx) but I found that using those examples would not display the validation summary with client side validation which is not much use to me.
The solution I am looking for would need to allow both client and server side validations to appear for the correct form. Does anyone have an example of something like this using MVC? I'd like to avoid manually looking after the client side validation using jquery if possible and just to use the framework to handle the work for me.
Thanks for your suggestions,
Rich
After playing around with this issue for longer than I care to admit, the answer as always was simple once you know how!
The solution to this issue for anyone else who comes up against it is to rename your action so your POST action is a different name to your GET action. This way, when the Layout page is posted back and triggers the PartialView to also be posted back there is only a GET action for your PartialView so the ValidationSummary will not be triggered.
Based on my example above I have altered the form to postback to another action for my login partial view and this has solved this issue for me.
In my Layout page the link to the partial in the store area remains the same:
#Html.Action("LogOn", "Account", new { area = "Store" })
The logon form action is then altered to point to a new action - not called the same name as the GET action:
#using (Ajax.BeginForm("ConfirmLogon", "Account", new { #area = "Store" }, new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "LoginContainer", LoadingElementId = "actionLoaderImage" }, new { id="LogonForm" }))
{
....
}
Then I just renamed my controller action so that it has a different action name for the POST action so that when the layout page is called using a post and the partial is loaded up with a POST action that it doesn't call my logon POST action:
// POST: /Account/ConfirmLogon
[HttpPost]
public ActionResult ConfirmLogon(LogOn_ViewModel model)
{
if (ModelState.IsValid)
{
if (SecurityService.Login(model.UserName, model.Password, model.RememberMe))
{
return PartialView("LoggedIn");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return PartialView("Logon",model);
}
Hopefully this will help some others out with this issue. Seems so obvious now but drove me nutty :D

Get route values in ajax action + Asp.net MVC 3

In Post Controller , URL is like this :
http://127.0.0.1/post/5006/some-text-for-seo-friendly
{contoller}/{id}/{seo}
public ViewResult Index(){
.....
}
I used Ajax.BeginForm in index view and mapped it to AddComment action in the same controller.
#using (Ajax.BeginForm("AddComment", "Post", new AjaxOptions()
{
HttpMethod = "GET",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "comment-container"
}))
{
<textarea cols="2" rows="2" name="comment" id="comment"></textarea>
<input type="submit" value="Add Comment" />
}
and in controller
public PartialViewResult AddComment(string comment){
// how can I get 5006 {id} here
}
my question is how can I get the {id} [5006] in AddComment action.
note : the hard way is using Request.UrlReferrer and split by '/' and select form array .
You need to supply the id to the BeginForm method using this overload which takes a routeValues parameter:
#using ( Ajax.BeginForm( "AddComment", "Post",
new { id = 5006 },
new AjaxOptions
{
...
Then you should just be able to take the id as a parameter of your action method:
public PartialViewResult AddComment( int id, string comment )
{
...
MVC will call AddComment with the populated id value.

Why does Html.BeginForm generate empty action?

I have a controller in an area called Admin
public class SiteVisitController : Controller
{
public ViewResult ReadyForCompletion() { ... }
public ViewResult CompleteAndExport() { ... }
}
and a view (ReadyForCompletion.cshtml) that has posts back to a different controller action on the same class
#using (Html.BeginForm( "CompleteAndExport", "SiteVisit" ))
{
<input type="submit" value="Complete & Export" />
}
The generated HTML for this form has a blank action:
<form action="" method="post"> <input type="submit" value="Complete & Export" />
</form>
I want to know why this has a blank action? For more info, I also added in a
#Url.RouteUrl(new { controller = "ReadyForCompletion", action = "SiteVisit", area = "Admin" })
which also printed out an empty string. Also, if I use an empty Html.BeginForm() it generates the correct action.
Registered routes are
context.MapRoute(
"Admin_manyParams",
"Admin/{controller}/{action}/{id}/{actionId}",
new { action = "Index", id = UrlParameter.Optional, actionId = UrlParameter.Optional }
);
I believe your problem is caused by having consecutive optional parameters. I was not able to replicate your problem until I changed the route to contain two optional parameters.
See: This article which explains the problem
For those of you encountering this issue using ASP.NET Core the root cause is the same, though the solution is slightly different. I first saw this in Core using multiple default values when calling .MapRoutes(). E.g.
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Foo", action = "Bar" }
);
The workaround is to place the default values into the string template:
routes.MapRoute(
name: "default",
template: "{controller=Foo}/{action=Bar}/{id?}"
);
YMMV.

Resources