Sitecore MVC Controller and Forms - ActionResult not rendering Layout on postback - asp.net-mvc-4

Sitecore 7.1v1, most recent Glass Mapper, MVC4. When we submit the form POST, we get no layout with the return View. I'd prefer not to have to redirect to another page since this is supposed to be a wizard-like experience. This is also lightweight enough not to require Ajax, although we could use it as a last resort. I can't find who to make sure that while returning the View that we get the layout as well. I'm new to Sitecore MVC and pretty new at MVC in general. The PageBase that's referenced is a Custom Model using Glass.
We have the following Controller Rendering:
public class RegistrationController : Controller
{
[HttpGet]
public ActionResult VerifyAccount()
{
return View("~/Views/Public/Wizards/Registration/VerifyAccount.cshtml",
new SitecoreContext().GetCurrentItem<PageBase>());
}
[HttpPost]
public ActionResult CreateProfile()
{
ViewBag.Name = Request["VerificationType"];
ViewBag.Step = 2;
return View("~/Views/Public/Wizards/Registration/CreateProfile.cshtml",
new SitecoreContext().GetCurrentItem<PageBase>());
}
}
The default action for this is VerifyAccount(). This renders as expected. The initial view is as follows:
#inherits Glass.Mapper.Sc.Web.Mvc.GlassView<Public.Model.GlassModel.Primary.PageBase>
<div>
<h3>#Editable(a => a.Title)</h3>
<p>
#Editable(a => a.Description)
</p>
<hr />
#using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
{
#Html.Sitecore().FormHandler("Registration", "CreateProfile")
#Html.ValidationSummary(true, "Verification Not Selected.")
<fieldset>
#Sitecore.Globalization.Translate.Text("$Registration.VerificationTitle")
#{ var validations = new SitecoreContext().GetItem<GlassFrameBase>(Guid.Parse("{3694FC43-3DB7-470A-A1E9-2649856AAF10}"));}
<select id="VerType" name="VerificationType">
#foreach (var validation in validations.GetChildren<VerificationMethod>())
{
<option value="#validation.MethodValue">#validation.MethodName</option>
}
</select>
<input type="submit" value="Next" />
</fieldset>
}
This posts back to the CreateProfile() Method. This part works great. The only issue is that when it returns the view this time, it returns just the view without the layout.
The final view is as follows:
#using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
{
#Html.Sitecore().FormHandler()
<p>
<b>Verification Type Was: </b>#ViewBag.Name
</p>
<p>#ViewBag.Step</p>
<input type="hidden" value="ThisIsATest" name="TestHidden" id="TestHidden"/>
<input type="submit" name="back" value="Back" /><br />
<input type="submit" name="next" value="Next" />
}
Everything else is working exactly as expected but I'm missing something important that loads the Layout on the return trip.

I have noticed this before as well and I think it relates to this line:
#Html.Sitecore().FormHandler("Registration", "CreateProfile")
It seems to bypass the standard rendering pipeline and just call the target action. I have written a blog post on how you can control calls to different action on multiple controllers. this might help:
http://www.experimentsincode.com/?p=425

Try changing the return type of CreateProfile from ActionResult to PartialViewResult, and then return View("... to return PartialView("...
Also, here's a post about what you can return for Sitecore Controller Renderings.
http://mhwelander.net/2014/04/09/sitecore-controller-rendering-action-results-what-can-i-return/
I haven't looked deeply into form posting with Controller Renderings, but if the above suggestion doesn't work then maybe consider the execution lifestyle used in Sitecore MVC (mentioned in the post).

Related

How to change text from in HTML Label from backend

I'm starting to learn ASP.NET Core MVC and just found out that the toolbox is inaccessible/disabled/grayed out, so in html I cannot use <asp:Label/> tag and have to use <label></label> tag instead.
Now I am having trouble changing the string on the HTML Tag <label></label> from the backend.
For this case I already wrote runat="server" inside the tag but still an error occured that says:
The name 'lblkpj' does not exist in the current context
Here is the example html:
<label class="text-center mb-1 fw-bold" runat="server" id="lblkpj"> </label>
and C#:
if (tbl.Rows.Count > 0)
{
lblkpj.text = "Success";
}
else
{
lblkpj.text = "Failed";
}
Where am I going wrong?
Work with Razor markup.
You can implement the logic in View by declaring the variable. And use # symbol. Razor will evaluate the expression and render the output into HTML.
And make sure that you need to return the ViewModel as a List of your model objects from the Controller to View.
View
#model List<YourModel>
#{
string status = Model.Rows.Count > 0 ? "Success" : "Failed";
}
<label class="text-center mb-1 fw-bold" id="lblkpj">#status</label>
Controller
public class YourController : Controller
{
public IActionResult YourView()
{
// Mock data
List<YourModel> list = new List<YourModel>();
return View(list);
}
}

BotDetect and ASPNET Razor Pages is not validating

I have decided to use BotDetect Captcha in my project to stop spam, however, I have not been able to check if the user has entered the correct captcha since Razor Pages doesn't support Filters.
On their site, they say to use this attribute to check if the captcha is valid
[CaptchaValidationActionFilter("CaptchaCode", "ExampleCaptcha", "Wrong Captcha!")]
However, razor pages doesn't allow attributes on page methods.
Digging into the source code of the attribute, I found this
MvcCaptcha mvcCaptcha = new MvcCaptcha(this.CaptchaId);
if (mvcCaptcha.IsSolved) { }
However when I tried that code directly in the OnPost method, mvcCaptch.IsSolved always returns false.
Checking the session variables also shows all of the BDC_ values required for this control to work so I've hit a wall here. Hoping someone could help me out. Thanks.
Official docs if it helps, although, I could'nt find any reference to Razor Pages on the site https://captcha.com/mvc/mvc-captcha.html
I found there is an attribute CaptchaModelStateValidation attribute you can apply to a Razor page model property that is bound to the captcha code input. This way you get the validation automatically in the ModelState.
Here is a sample model that validates the captcha.
public class CaptchaValidatorModel : PageModel
{
public void OnPost()
{
if (ModelState.IsValid)
{
// Perform actions on valid captcha.
}
}
[BindProperty]
[Required] // You need this so it is not valid if the user does not input anything
[CaptchaModelStateValidation("ExampleCaptcha")]
public string CaptchaCode { get; set; }
}
The page uses the code provided in the documentation sample.
#page
#model CaptchaWebApplication.Pages.CaptchaValidatorModel
#{
ViewData["Title"] = "Captcha";
}
<form method="post">
<label asp-for="CaptchaCode">Retype the code from the picture:</label>
<captcha id="ExampleCaptcha" user-input-id="CaptchaCode" />
<div class="actions">
<input asp-for="CaptchaCode" />
<input type="submit" value="Validate" />
<span asp-validation-for="CaptchaCode"></span>
#if ((HttpContext.Request.Method == "POST") && ViewData.ModelState.IsValid)
{
<span class="correct">Correct!</span>
}
</div>
</form>

How do i get the URL from the caller of a HTTP Request?

Let's say that i have a navbar with a search bar, and it appears on every single page, so when the user click on the search button, it could happend 2 things:
Refresh the page if the input is empty
Redirect to a SearchResult.cshtml and show the results...
So the controller and action who handle the HTTP POST Request are Search - SearchResult respectively. I want to get the URL from the caller because the action method will need it to decide what page should redirect it.
PD: I'm using ASP.NET CORE MVC 3.1
In the Layout page, you could add a hidden field to store the current page URL, then, when click the Search button, in the SearchResult action, you could get the previous URL from the hidden field. Code as below:
_Layout.cshtml page,
<div class="search">
#{
//get current url
Uri currenturl = new Uri(String.Format("{0}://{1}{2}{3}", Context.Request.Scheme, Context.Request.Host, Context.Request.Path, Context.Request.QueryString));
}
<form asp-controller="Search" asp-action="SearchResult">
<input class="text-body" type="text" value="" name="search" />
<input name="returnurl" type="hidden" value="#currenturl" />
<input type="submit" class="btn btn-info" value="Search" />
</form>
</div>
Code in the SearchResult action:
public IActionResult SearchResult()
{
//get the returned url
var returnurl = HttpContext.Request.Form["returnurl"].ToString();
//do something
return Redirect(returnurl);
}
The screenshot as below:
What you probably want to do is submit the current path/query along with your query.
You could accomplish that by having your search form be something like this.
<form asp-controller="Search" method="get">
<input name="q" value="#Context.Request.Query["q"]" />
<input name="r" type="hidden" value="#Context.Request.GetEncodedPathAndQuery()" placeholder="Search the thing" />
</form>
Notice im using a get method here. This is to keep the search state in the browser but you can manage that however you need too. Im also using GetEncodedPathAndQuery which will need to be installed, but again the important part is to maintain the path/query(and more if this will be cross domain).
My controller will then look something like this.
[Route("/search")]
public class SearchController : Controller
{
[HttpGet]
public IActionResult Index(
[Bind(Prefix = "q")] string query,
[Bind(Prefix = "r")] string referrer)
{
IActionResult result = null;
if (string.IsNullOrWhiteSpace(query))
{
result = this.Redirect(referrer);
}
else
{
result = this.View(); // do search and return view.
}
return result ?? this.Redirect("/");
}
}
This can obviously be adjusted in anyway you need. The main thing is accepting the two values from the request and handling them properly. You might also want to validate that the referrer is an actual url since the redirect action will throw if its not.

Get Partial View by Ajax Call and Display Modal Single Value

I know I am missing something very basic. I have a HomeController and inside that there is a partial view. There is a button inside partial view which gets data(ParserModel) via ajax call. Now the single data value is not getting in _Parser.cshtml.
Here is my HomeController ajax method which calls ParserView data.
public IActionResult Parser()
{
ParserModel parser = new ParserModel();
parser.LogoPosition = "This is correct";
return PartialView("_Parser");
}
Below is my _Parser.cshtml view which is not setting the value of LogoPosition
#using PageRefine.Models
#model PageRefine.Models.ParserModel
<form asp-controller="Home" asp-action="Parser" data-ajax="true" data-ajax-update="#CustomerList">
<button type="submit" id="button1">Click</button>
<p>#Model.LogoPosition</p>#*This is giving null reference error. Here it should be "This is correct"*#
<p>#Html.RenderPartial("_Parser", Model.LogoPosition)</p>#*This is printing LogoPosition*#
</form>
Here is Index.cshtml code for partial view
<div id="CustomerList">
<partial name="_Parser" />
</div>
What should I do to get the modal values by their name in Partial View (_Parser.cshtml). Let me know if I am missing any info.
You need to pass the model to your partial view
public IActionResult Parser()
{
ParserModel parserModel = new ParserModel();
parserModel.LogoPosition = "This is correct";
return PartialView("_Parser", parserModel);
}

How can I post the same data to two different handlers depending on the button clicked?

[See updates at bottom]
I have a Razor page with a form on it. I want to have two buttons on that form, that perform a slightly different action - both using the same posted form data.
I tried using the asp-page-handler helper on the second button, but it doesn't seem to add anything to the HTML (I would expect it to add a formaction attribute to the <button> element, but it doesn't add anything at all).
Here's an example page:
#page "{id?}"
#model IndexModel
#tagHelperPrefix x:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Current value is #Model.Foo</p>
<x:form method="post">
<input type="text" name="foo" />
<button type="submit">Default</button>
<button type="submit" x:asp-page-handler="Alternative">Alternative</button>
</x:form>
... and here's the corresponding page model:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyWebApplication.Pages
{
public class IndexModel : PageModel
{
[BindProperty]
public string Foo { get; set; }
public void OnGet(int? id)
{
}
public void OnPostAsync(string foo)
{
Foo = foo;
}
public void OnPostAlternativeAsync(string foo)
{
Foo = foo.ToUpper();
}
}
}
This is rendered as:
...where the generated HTML for the form is:
<form method="post">
<input type="text" name="foo" />
<button type="submit">Default</button>
<button type="submit" x:asp-page-handler="Alternative">Alternative</button>
</form>
The fact that the x:asp-page-handler attribute is still in the generated HTML makes me think that the Razor engine hasn't recognized it. I've tried taking off the x: prefix, but that didn't help.
What am I doing wrong?
UPDATE
OK, I tried removing the tag prefix and removing the #tagHelperPrefix line, and that made a difference. A formaction is added to the second <button> element as expected.
However:
that's really annoying - the #tagHelperPrefix is not something I want to lose, and
now both buttons are triggering the "Alternative" action, even though only one of them has the formaction!
Here's the new generated HTML:
<form method="post">
<input type="text" name="foo" />
<button type="submit">Default</button>
<button type="submit" formaction="/?handler=Alternative">Alternative</button>
</form>
SECOND UPDATE
OK, so If I put asp-page-handler="" on the "default" button, then each button goes to the correct handler, which is fine.
The last question that remains, then, is: how can I make this work with the tag helper prefix?
[Answering my own question in case this helps others.]
It turns out that:
The tag-helper-prefix only applies to elements, not attributes, so it should be asp-page-handler="..." rather than x:asp-page-handler="..." even if the tag-helper-prefix is x:.
Those asp- attributes are only recognized within a tag that is tag-helper-enabled - which is all elements when no tag-helper-prefix is specified, or only elements with the tag-helper-prefix where one is specified. In my case, I had to change <button ...> to <x:button ...>.
If you specify asp-page-handler for one button, you need to specify it on all the buttons, even if you specify it as "" to get the default action.