MVC 4 URL bypassing - url

I am creating a web application where there are five steps.
Home Page1 Page 2 Review Confirmation.
In the url, it goes like localhost:22112/Home/Page1 Page 2 and so forth.
My problem is if someone copies localhost:22112/Home/Page2, then it skips everything
and jumps to page 2 directly. So, how can I stop that? I did the following but its not working properly.
Any suggestions would be really helpful.
In the controller
private bool IsFromIndexPage()
{
if (Session["IsFromIndex"] != null)
{
return true;
}
else
{
return false;
}
}
And for each page actionresult, I am writing it like this.
[HttpGet]
public ActionResult Page1()
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpPost]
public ActionResult Page1(Information model, string command)
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpGet]
public ActionResult Page2()
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpPost]
public ActionResult Page2(Information model, string command)
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}

If you're using session to store the progress through the steps you should be checking your session variables to validate the request is for the given page otherwise redirect the user to the first/current completed page.
You can write a custom request handler for this to keep your session validation code separate your controller code
see this Question about how to implement the basic functionality to what you want to do
EDIT:
switch(currentStep){
case 1:
return Step1(model)
break;
case 2:
return Step2(model)
break;
default:
return new HttpNotFoundResult();
break;
}

Here is a bit different approach, on how to make a wizard with asp.net MVC using ajax.
Your url will be /Home/Wizard on every step. Since using the AjaxOnly attribute, it will not be possible to visit Step1, Step2 etc (see reference in the bottom for AjaxOnly)
Controller:
public ActionResult Wizard()
{
return View();
}
[AjaxOnly]
public ActionResult Step1()
{
return PartialView("Step1");
}
[AjaxOnly]
public PartialViewResult Step2(FormCollection coll)
{
Session["FullName"] = coll["FullName"]!= null ? coll["FullName"].ToString() : string.Empty;
return PartialView("Step2");
}
[AjaxOnly]
public PartialViewResult Confirm(FormCollection coll)
{
WizardModel model = new WizardModel() { Name = Session["FullName"].ToString(), Phone = coll["Phone"] != null ? coll["Phone"].ToString() : string.Empty };
return PartialView("Confirm", model);
}
Model for last step:
public class WizardModel
{
public string Phone { get; set; }
public string Name { get; set; }
}
Make sure you reference jquery.unobtrusive-ajax in your page/layout page
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
Wizard.cshtml
#{
ViewBag.Title = "Wizard";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Wizard - Overview</h2>
#using (Ajax.BeginForm("Step1", new AjaxOptions { HttpMethod="Get", UpdateTargetId = "wizardcontainer" }))
{
<input type="submit" value="Start wizard" />
}
<div id="wizardcontainer"></div>
Step1.cshtml
<div>
<h2>Wizard - Step 1</h2>
<br />
#using(Ajax.BeginForm("Step2", new AjaxOptions { UpdateTargetId = "wizardcontainer" }))
{
#Html.Label("FullName")
#Html.TextBox("FullName")
<input type="submit" value="Next >>" />
}
</div>
Step2.cshtml
<div>
<h2>Wizard - Step 2</h2>
#using(Ajax.BeginForm("Confirm", new AjaxOptions { UpdateTargetId = "wizardcontainer" }))
{
#Html.Label("Phone")
#Html.TextBox("Phone")
#Ajax.ActionLink("<< Previous", "Step1", new AjaxOptions { UpdateTargetId = "wizardcontainer" })
<input type="submit" value="Next >>" />
}
</div>
Confirm.cshtml
#model MvcApplication2.Controllers.WizardModel
<div>
<h2>Wizard - Final Stage</h2>
Name: #Model.Name
<br />
Phone: #Model.Phone
#Ajax.ActionLink("<< Previous", "Step2", new AjaxOptions { UpdateTargetId = "wizardcontainer" })
</div>
Look here for the AjaxOnly attribute:
http://helios.ca/2009/05/27/aspnet-mvc-action-filter-ajax-only-attribute/

Related

Hijacked Umbraco HttpPost action not firing

I've hijacked the route in Umbraco 7.1 and for some reason my HttpPost is not firing when the submit button is pressed. Any input as to why this is? There is a postback taking place when send is pressed but the when putting a break point in the HttpPost it's never fired.
Here's a snippet of my code, the markup followed by the controller.
#inherits UmbracoViewPage
#{
Layout = "Layout.cshtml";
}
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.TextAreaFor(m => m.Message)
< i n p u t type="submit" value="Send" />
#Html.ValidationMessageFor(m => m.Message)
</div>
}
public ActionResult Index(ManageMessageId? smess)
{
var errorModel = new ErrorModel();
...
return CurrentTemplate(errorModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(ErrorModel model)
{
if (ModelState.IsValid)
{
...
}
return View();
}
Assuming you are using SurfaceControllers, you would want to create your form as follows. Note the change in how you create the form and how the generic and parameter match that of the surface controller:
#using (Html.BeginUmbracoForm<MyController>("Index"))
{
}
Your controller should look something like:
public class MyController : SurfaceController
{
public ActionResult Index(ManageMessageId? smess)
{
var errorModel = new ErrorModel();
...
return CurrentTemplate(errorModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(ErrorModel model)
{
if (ModelState.IsValid)
{
...
}
return View();
}
}

How do I get a strongly typed DropDownList to bind to a control Action

I've just started a new MVC project and I'm having trouble getting the post result from a form.
This is my Model Class :
public class User
{
public int id { get; set; }
public string name { get; set; }
}
public class TestModel
{
public List<User> users { get; set; }
public User user { get; set; }
public SelectList listSelection { get; set; }
public TestModel()
{
users = new List<User>()
{
new User() {id = 0, name = "Steven"},
new User() {id = 1, name = "Ian"},
new User() {id = 2, name = "Rich"}
};
listSelection = new SelectList(users, "name", "name");
}
}
This is my view class
#model MvcTestApplicaiton.Models.TestModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
#if (#Model.user != null)
{
<p>#Model.user.name</p>
}
And this is my controller :
public class TestModelController : Controller
{
public TestModel model;
//
// GET: /TestModel/
public ActionResult Index()
{
if(model ==null)
model = new TestModel();
return View(model);
}
[HttpPost]
public ActionResult Test(TestModel test)
{
model.user = test.user;
return RedirectToAction("index", "TestModel");
}
}
The drop down list appears just fine but I can't see to get the ActionResult Test function to run. I thought it would just bind itself with reflection but whatever is wrong, I can't see it.
You have two main errors in your code.
As Brett said you're posting to the Index method, but you don't have Index method that supports POST verb. The easiest way to fix is to change Html.BeginForm() with Html.BeginForm("Test", "TestModel")
You're using Html.DropDownListFor in a wrong way. You could pass only a value types there, because don't forget that the View will generate an HTML page. So instead of User in your Model you should have an UserID and in your View you should have #Html.DropDownListFor(x => x.UserID, #Model.listSelection). And finally in your Action you should query your data source to get the details for the user with this ID.
Hope this helps.
Looks like you're posting back to index. Either use a GET Test() action method, or specify the ACTION parameter in BeginForm().
For example,
#using (Html.BeginForm("Test", "TestModel"))
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
Or use a view named Test (rename index.cshtml to test.cshtml):
public ActionResult Test()
{
if(model ==null)
model = new TestModel();
return View(model);
}

Submit button mapped to Controller action without related View. Returning different view based on situation

In MVC4, a controller named UserController contains an action ForgotPassword and a respective view is also created. This view contains one textbox and one submit button. On the submit of this button the email id should be verified, if not found in record, it should show a error message on the same view, else navigate to different view.
I have used Begin Form for mapping the submit button to a new action VerifyEmailId in UserController. However I am stuck on passing the view based on the validity of the email id. Note that this action VerifyEmailId does not have any related view.
Please suggest the best way to do it.
View Code:
#model UIKendoLearning.Models.UserDetails
<h2>Forgot Retrieval</h2>
#using (Html.BeginForm("ForgotPassword", "User"))
{
<div id="PasswordRetrieval">
#Html.Label("Please enter your registered email address: ")
<input type="email" id="Email" value="#Model.Email" name="Email" placeholder="e.g. myname#example.net" />
#Html.ValidationMessageFor(m => m.Email)
<br />
<input type="submit" name="Submit" value="Generate New Password" />
</div>
}
Controller Code:
public class UserController : Controller
{
//
// GET: /User/
public ActionResult ForgotPassword()
{
return View(new UserDetails());
}
[HttpPost]
public ActionResult SendNewPassword(UserDetails userInfo)
{
try
{
if (userInfo != null)
{
HttpClient server = new HttpClient();
server.DefaultRequestHeaders.Add("EmailId", userInfo.Email);
HttpResponseMessage response = server.GetAsync("http://localhost/BankService/api/Account/ValidateUser").Result;
if (response.StatusCode != HttpStatusCode.Found)
{
return RedirectToAction("ForgotPassword", "User");
}
}
}
catch (Exception ee)
{
}
return RedirectToAction("Index", "Home");
}
I have a similar implementation, I suggest you don't need a new Action ValidateEmailID, but only a bool function that you can call from the ForgotPassword Post Action.
This could be the Controller code:
//
// GET: /User/ForgotPassword
[AllowAnonymous]
public ActionResult ForgotPassword()
{
return View();
}
//
// POST: /User/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(String email)
{
if (ModelState.IsValid)
{
if (verifyEmailId(email))
{
// Do something, i.e. send email with proper instruction
// Display a view to say action is done!
return View("ConfirmReset");
// Or redirect to another action:
return RedirectToAction("ActionName", "ControllerName");
}
else
{
// Add an error message
ModelState.AddModelError("", "The email ID you submitted is unknown");
}
}
// Redisplay the ForgotPassword View with error message, preserving the submitted email value
ViewBag.email = email;
return View();
}
private bool verifyEmailId(email) {
// Your own code
// return true if email is valid
}
And this could be the ForgotPassword View code:
<p>Please submit the email address to reset the password</p>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.Label("email")
#Html.Editor("email")
<input type="submit" value="Reset Password" />
}
I hope this could be usefull for you.
Modify your method
#model UIKendoLearning.Models.UserDetails
<h2>Forgot Retrieval</h2>
#using (Html.BeginForm("ForgotPassword", "User"))
{
<div id="PasswordRetrieval">
#ViewBag.Error **// Your Error comes here**
#Html.Label("Please enter your registered email address: ")
<input type="email" id="Email" value="#Model.Email" name="Email" placeholder="e.g. myname#example.net" />
#Html.ValidationMessageFor(m => m.Email)
<br />
<input type="submit" name="Submit" value="Generate New Password" />
</div>
}
Controller
public ActionResult ForgotPassword()
{
if (Request.QueryString["error"] != null)
ViewBag.Error = "Email Not Found";
else
ViewBag.Error = "";
return View(new UserDetails());
}
[HttpPost]
public ActionResult SendNewPassword(UserDetails userInfo)
{
try
{
if (userInfo != null)
{
HttpClient server = new HttpClient();
server.DefaultRequestHeaders.Add("EmailId", userInfo.Email);
HttpResponseMessage response = server.GetAsync("http://localhost/BankService/api/Account/ValidateUser").Result;
if (response.StatusCode != HttpStatusCode.Found)
{
return RedirectToAction("ForgotPassword", "User",new{error="notFound"});
}
}
}
catch (Exception ee)
{
}
return RedirectToAction("Index", "Home");
}
As #Doan Cuong wrote it is good solution to use content validity. For example, you have model for your view like this:
class UserDetails
{
[DataType(DataType.EmailAddress)]
[Required(ErrorMessage = "Please enter a proper email address")]
public string Email { get; set; }
// property for displaying custom error message if validation failed
public string ErrorMessage { get; set; }
...
}
You have a form which calls action ForgetPassword. SendNewPassword should be renamed to ForgetPassword. And if you using try/catch it will be good if you display something if error occures. So, the controller code may look like this:
class UserController
{
[NonAction]
private bool VerifyEmail(string email)
{
// ... verify logic
}
public ActionResult ForgotPassword()
{
return View(new UserDetails());
}
[HttpPost]
public ActionResult ForgotPassword(UserDetails userInfo)
{
if (ModelState.IsValid)
{
// Here is userInfo.Email is a proper email address
if(VerifyEmail(userInfo.Email)
{
HttpClient server = new HttpClient();
server.DefaultRequestHeaders.Add("EmailId", userInfo.Email);
HttpResponseMessage response = server.GetAsync("http://localhost/BankService/api/Account/ValidateUser").Result;
if (response.StatusCode != HttpStatusCode.Found)
{
// Here is error action ForgotPassword with param with field "error"
// return RedirectToAction("ForgotPassword", "User",new{error="notFound"});
return View("ForgotPassword", "User", new UserDetails { ErrorMessage = "Email not found" } );
}
// Here is all ok - go home
return RedirectToAction("Index", "Home");
}
}
// Redisplay form with error messages
return View(model);
}
}
And modify the View code:
#model UIKendoLearning.Models.UserDetails
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<h2>Forgot Retrieval</h2>
#using (Html.BeginForm("ForgotPassword", "User"))
{
<div id="PasswordRetrieval">
#Html.Label("Please enter your registered email address: ")
<input type="email" id="Email" value="#Model.Email" name="Email" placeholder="..." />
#Html.ValidationMessageFor(m => m.Email)
#if(Model.ErrorMessage != null)
{
#* you can insert some good html *#
<span>#Model.ErrorMessage</span>
}
<br/>
<input type="submit" name="Submit" value="Generate New Password" />
</div>
}

How to replace image in ASP MVC 4 page

I have a Model class which knows to return current image, next image and previous image.
I have a Controller with an Action for switching slides.
That Action is triggered by Ajax.BeginForm which have a buttons for previous slide and next slide.
What can I do in the Action to make the view change slide without a page refresh?
Here is what I came up with so far:
Controller:
public class SlideshowController : Controller
{
static SlideshowModel slideshowModel = new SlideshowModel();
//
// GET: /Slideshow/
public ActionResult Index()
{
return View(slideshowModel);
}
public ActionResult GetCurrentSlideImage()
{
var image = slideshowModel.CurrentSlideImage;
return File(image, "image/jpg");
}
[HttpPost]
public ActionResult SlideshowAction(SlideshowModel model, string buttonType)
{
if (buttonType == ">")
{
slideshowModel.IncrementSlideNumber();
}
if (buttonType == "<")
{
slideshowModel.DecrementSlideNumber();
}
return JavaScript("alert('what to return to update the image?')");
}
}
View:
#model PresentationWebServer.Models.SlideshowModel
#using (Ajax.BeginForm("SlideshowAction", new AjaxOptions { UpdateTargetId = "result" }))
{
<img src="#Url.Action("GetCurrentSlideImage") " id="result"/>
<br />
<input class="float-left" type="submit" value="<" name="buttonType" id="btn-prev" />
<input class="float-left" type="submit" value=">" name="buttonType" id="btn-next" />
}
Very basic action
public class SlideshowController : Controller
{
...
public ActionResult GetCurrentSlideImage()
{
return Json("newImagePath.jpg");
}
...
}
On the client side add a jQuery ajax call :
<script>
function changeImage() {
$.ajax({
url: 'Slideshow/GetCurrentSlideImage',
type: post,
contentType: 'application/json',
success: function(data) {
$("#imageIDToReplace").attr("src", data);
},
complete: complete
}
});
}
</script>
Code not tested, but should give you the right idea
Edit:
You can return the image as data using image id in a new Action like this:
public ActionResult Image(string id)
{
var dir = Server.MapPath("/Images");
var path = Path.Combine(dir, id + ".jpg");
return base.File(path, "image/jpeg");
}
taken from Can an ASP.NET MVC controller return an Image?
then you can replace the src in the image attribute with the Action address
like -
http://localhost/MyController/Image/MyImage

Interesting Asp .net mvc example

I was trying a basic asp .net mvc project configuration explained below;
Before first submit, the result is left one in photo which is expected, after submit textbox(#Html.TextBoxFor(model => model.Name)) and text(#Model.Name) shows different values as seen in right one in photo, unexpectedly; why is it so? They show different values although their model is unique.
Model:
public class Personnel
{
public string Name { get; set; }
}
View:
#model deneme.Models.Personnel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("Index", "Home"))
{
#Html.TextBoxFor(model => model.Name)
<br/>
<br/>
#Model.Name
<br/>
<br/>
<input type="submit" value="submit" />
}
Controller:
public ActionResult Index(Personnel personnel)
{
if (string.IsNullOrEmpty(personnel.Name))
{
personnel.Name = "Ahmet";
}
else
{
personnel.Name = personnel.Name + "server";
}
return View(personnel);
}
Edit: My previous answer didn't answer your question.
If you add ModelState.Clear() to the controller action, it will work as you want it to.
public ActionResult Index(Personnel personnel)
{
if (string.IsNullOrEmpty(personnel.Name))
{
personnel.Name = "Ahmet";
}
else
{
personnel.Name = personnel.Name + "server";
}
ModelState.Clear();
return View(personnel);
}

Resources