How to preserve ReturnUrl query parameter when submiting form - razor-pages

I'm using cookie based authentication with Razor Pages.
When the user does not have an authentication cookie in the browser and tries to access a razor page with, for exmaple, https://localhost:8080/admin/protected
by default the aspnet core framework redirects to the LoginPath specified in CookieAuthenticationOptions. If that Url is something like /login, then the browser is redirected automatically to /login?ReturnUrl=%2Fadmin%2Fprotected
That's perfect. The problem is that I don't know how this is supposed to work later, because if my /login razor page submits a form (for example it posts the username and password), the ReturnUrl parameter is lost.
What's the "right" way to preserve this? I'm trying to find something in the documentation without success, nor I can find any asp tag helper.
PS: I was following this as a guide: https://www.mikesdotnetting.com/article/335/simple-authentication-in-razor-pages-without-a-database

I implemented a workaround, although it looks "hacky" and I was expecting to have some syntatic sugar from the Razor pages to preserve this ReturnUrl.
In any case, the solution is to include in my form model a field where I will store the ReturnUrl query parameter when the page renders (e.g: Get request). For example:
public class LoginForm
{
public string ReturnUrl { get; set; } = string.Empty;
}
and in my razor page code:
public async Task OnGetAsync()
{
var nameValueCollection = HttpUtility.ParseQueryString(Request.QueryString.Value);
const string returnUrlKey = "returnUrl";
var returnUrl = nameValueCollection.Get(returnUrlKey);
LoginForm.ReturnUrl = returnUrl; //here I save the value on the form
// do other things
}
Now in my razor page I add a hidden field to pass that value back to the server when posting the form. For example:
<form method="post">
<input asp-for="LoginForm.ReturnUrl" type="hidden" />
<button type="submit" asp-page-handler="Login">Login</button>
</form>
And then I can access the original value in
public async Task<IActionResult> OnPostLoginAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Redirect(LoginForm.ReturnUrl);
}
Notice that the ReturnUrl value is preserved even if there is a validation error and the ModelState is invalid, which is what I want.

Related

Add querystring to "asp-page" link in CORE razor pages

In CORE razor pages
<a asp-page="View" asp-route-ID="#item.ID" >View</a>
this creates a link to a page using a route to pass the ID and generates the following HTML
View
I want to add the contents of the querystring to this link so the generated HTML looks like
View
#Request.QueryString gets the entire querystring e.g. "?s=smith" but I can't find the way to add it to the end.
Any ideas.
This works for me (from https://www.learnrazorpages.com/razor-pages/tag-helpers/anchor-tag-helper):
<a asp-area="Identity" asp-page="/Account/Register" asp-route-view="personal">Text</a>
This routes you to: Identity/Account/Register?view=personal.
In "asp-route-view", the view can be anythin you need:
<a asp-area="Identity" asp-page="/Account/Register" asp-route-disco="party">Text</a>
routes you to Identity/Account/Register?disco=party.
Example Url :
https://localhost:44313/dados_pessoais?userid=1
Use: asp-route-youproperty="value"
Get user data sample
<a class="nav-link text-dark" asp-area="" asp-page="/dados_pessoais" asp-route-userid="1">Dados Pessoais</a>
c#
public void OnGet(int userid)
{
var usuario = Usuarios.Where(u => u.Id == userid).FirstOrDefault();
}
The asp router tag helpers will ensure the target page matches the format specified but assuming it does you can do something like the following:
<a asp-page="View" asp-route-ID="#item.ID" asp-route-s="#Request.Query["s"].ToString()">View</a>
The Request Query property can be used to access individual querystring parameters by name.

The required anti-forgery cookie "__RequestVerificationToken" is not present. I'm out of ideias

I'm developing a backend in MVC 5 for a client to update in their website. However I got across this error:
Error Image
This is the controller with the methods with the AntiForgeryToken
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
[System.web.Mvc.AuthorizeSectionccess(sectionname = "IT")]
public ActionResult Create()
{
return View();
}
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
[System.web.Mvc.AuthorizeSectionccess(sectionname = "IT")]
[System.web.Mvc.AuthorizePermitionAccess(PermissonType = "Add")]
[HttpPost]
public ActionResult Create(Welcome_conteudoPage model)
{
DB.Welcome_conteudoPage.Add(model);
DB.SaveChanges();
return Redirect("Index");
return View(model);
}
And this is the View
#using (Html.BeginForm("Create", "ConteudosPageController", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
#Html.TextAreaFor(model => model.ConteudoStandard)
</div>
<div>
<input type="submit" value="Inserir" class="btn btn-primary"/>
</div>
<div>
Texto:
#Html.DisplayFor(model => model.ConteudoStandard)
</div>
}
I'm using the AntiForgeryToken on both ends and still get that error. I know that there are thousands of questions like this but I've tried all of the proposed solutions for 3 days and without any result.
EDIT: I forgot to mention that the view is going to call the controller and model for a tinyMCE Editor
It might not be the answer, however, you may have misunderstood what the anti forgery token does and where to use it.
Firstly, when you use #Html.AntiforgeryToken in a view, it registers something in either the session or cookie (can't remember which).
The validate anti forgery token attribute looks for that token and matches it against the passed in token in the hidden field. If it doesn't match, then most likely the post request didn't come from your view.
The thing to note, is that this requires a body parameter on the request to send in the token. You wouldn't have this on requests that don't have a body. A Get request doesn't have a body, and therefore doesn't need the validateantiforgerytoken attribute on it.

Orchard CMS - Extending Users with Fields - exposing values in Blog Post

I'd like to extend the users content definition to include a short bio and picture that can be viewed on every blog post of an existing blog. I'm unsure of what the best method to do this is.
I have tried extending the User content type with those fields, but I can't seem to see them in the Model using the shape tracing tool on the front end.
Is there a way to pass through fields on the User shape in a blog post? If so, what is the best way to do it?
I also have done this a lot, and always include some custom functionality to achieve this.
There is a way to do this OOTB, but it's not the best IMO. You always have the 'Owner' property on the CommonPart of any content item, so in your blogpost view you can do this:
#{
var owner = Model.ContentItem.CommonPart.Owner;
}
<!-- This automatically builds anything that is attached to the user, except for what's in the UserPart (email, username, ..) -->
<h4>#owner.UserName</h4>
#Display(BuildDisplay((IUser) owner))
<!-- Or, with specific properties: -->
<h1>#T("Author:")</h1>
<h4>#owner.UserName</h4>
<label>#T("Biography")</label>
<p>
#Html.Raw(owner.BodyPart.Text)
</p>
<!-- <owner content item>.<Part with the image field>.<Name of the image field>.FirstMediaUrl (assuming you use MediaLibraryPickerField) -->
<img src="#owner.User.Image.FirstMediaUrl" />
What I often do though is creating a custom driver for this, so you can make use of placement.info and follow the orchard's best practices:
CommonPartDriver:
public class CommonPartDriver : ContentPartDriver<CommonPart> {
protected override DriverResult Display(CommonPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Common_Owner", () => {
if (part.Owner == null)
return null;
var ownerShape = _contentManager.BuildDisplay(part.Owner);
return shapeHelper.Parts_Common_Owner(Owner: part.Owner, OwnerShape: ownerShape);
});
}
}
Views/Parts.Common.Owner.cshtml:
<h1>#T("Author")</h1>
<h3>#Model.Owner.UserName</h3>
#Display(Model.OwnerShape)
Placement.info:
<Placement>
<!-- Place in aside second zone -->
<Place Parts_Common_Owner="/AsideSecond:before" />
</Placement>
IMHO the best way to have a simple extension on an Orchard user, is to create a ContentPart, e.g. "UserExtensions", and attach it to the Orchard user.
This UserExtensions part can then hold your fields, etc.
This way, your extensions are clearly separated from the core user.
To access this part and its fields in the front-end, just add an alternate for the particular view you want to override.
Is there a way to pass through fields on the User shape in a blog post?
Do you want to display a nice picture / vita / whatever of the blog posts author? If so:
This could be your Content-BlogPost.Detail.cshtml - Alternate
#using Orchard.Blogs.Models
#using Orchard.MediaLibrary.Fields
#using Orchard.Users.Models
#using Orchard.Utility.Extensions
#{
// Standard Orchard stuff here...
if ( Model.Title != null )
{
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ( (string)Model.ContentItem.ContentType ).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
// And here we go:
// Get the blogPost
var blogPostPart = (BlogPostPart)Model.ContentItem.BlogPostPart;
// Either access the creator directly
var blogPostAuthor = blogPostPart.Creator;
// Or go this way
var blogPostAuthorAsUserPart = ( (dynamic)blogPostPart.ContentItem ).UserPart as UserPart;
// Access your UserExtensions part
var userExtensions = ( (dynamic)blogPostAuthor.ContentItem ).UserExtensions;
// profit
var profilePicture = (MediaLibraryPickerField)userExtensions.ProfilePicture;
}
#tag.StartElement
<header>
#Display(Model.Header)
#if ( Model.Meta != null )
{
<div class="metadata">
#Display(Model.Meta)
</div>
}
<div class="author">
<img src="#profilePicture.FirstMediaUrl"/>
</div>
</header>
#Display(Model.Content)
#if ( Model.Footer != null )
{
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
Hope this helps, here's the proof:

Craeting Custom Widget in Orchard 1.7

I am new to Orchard. So please forgive me if there is anything looking silly!
I want to create a custom widget for my Orchard website to encourage visitors to sign up for my Newsletter service. I have seen there is an option of using HTML widget but I want to create a new widget type like "Newsletter" which I shall use conditionally at AsideFirst block.
Is this possible to do? I only want to grab visitor's Name and Email address, and the form submission will be done using an action controller.
Do I have to create this widget through by-hand coding in VS? In fact I want to this way, not through the Orchard admin console.
Seeking for help. Any suggestion please?
Edit:
I have managed to create the widget following Sipke Schoorstra's suggestion. The area where I want to display the widget is now showing along with the the title I set from admin at the time of adding it to a zone. But the content (form elements) I created in the view is not displaying.
The View: (Views/NewsLetterSignupPart/NewsletterSignup.cshtml)
#model Emfluence.Intrust.Models.NewsLetterSignupPart
#{
ViewBag.Title = "Newsletter Signup";
}
#using (Html.BeginForm("NewsletterSignup", "NewsLetter", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="row-fluid">
<div class="span6">
<label>Name</label>
<input type="text" name="txtNewsletterUserName" required maxlength="50" style="width: 95%" />
<label>Email</label>
<input name="txtNewsletterUserEmail" type="email" required maxlength="85" style="width: 95%" />
<button class="btn pull-right">Submit</button>
</div>
</div>
}
Migration.cs
public int UpdateFrom15()
{
ContentDefinitionManager.AlterTypeDefinition(
"NewsletterWidget", cfg => cfg
.WithPart("NewsLetterSignupPart")
.WithPart("CommonPart")
.WithPart("WidgetPart")
.WithSetting("Stereotype", "Widget")
);
return 16;
}
NewsLetterSignupPart.cs
public class NewsLetterSignupPart : ContentPart<NewsletterSignupRecord>
{
[Required]
public string Name
{
get { return Record.Name; }
set { Record.Name = value; }
}
[Required]
public string Email
{
get { return Record.Email; }
set { Record.Email = value; }
}
}
And NewsletterSignupRecord.cs
public class NewsletterSignupRecord : ContentPartRecord
{
public virtual string Name { get; set; }
public virtual string Email { get; set; }
}
Where I am doing wrong?
The Custom Forms module is great if you don't want or need to code something yourself. In case you do want to handle form submissions yourself without using Custom Forms, this is what you could do:
Create a custom module
Create a migrations class that defines a new widget content type (see the docs for details on how to do this. Note: you don't need to create a custom part. You don't even need to create a migrations file to create a content type - you could do it using a recipe file. The nice thing about a migration though is that it will execute automatically when your module's feature is enabled).
Create a view specific for content items of your widget type (e.g. Widget-Newsletter.cshtml).
Inside of this view, write markup that includes a form element and input elements. Have this form post back to your controller.
Create your controller.
In the /admin interface, click Modules, on the Features` tab search for Custom Forms and click Enable. This will add a new Forms admin link on the left.
Next, create a custom content type (under Content Definition) called Newsletter, and add two fields (of type Text Field) called Name and E-mail.
Finally, click Forms and add a new Custom Form. Give it a title: this will be the default URL to access e.g. "Newsletter Form" will have a URL of /newsletter-form by Orchard defaults. Under Content Type select your newly created content type, Newsletter, from the dropdown. Customize anything else you want on this page, and click Publish Now
If you want to make this a widget, edit the content type and add the Widget Part. Create a layer with the rules you need and you can add the "Newsletter" widget to any zone you need on that layer.

How to submit a valid user request to Liferay Default Landing Page

I am using Liferay 6 .
I have created a page and added 6 portlets to it and configured inside portal-ext.properties file as
auth.forward.by.last.path=true
default.landing.page.path=/user/test/home
This private page with all my 6 portlets is shown only when i use Liferay's sign in Portlet (The default login page of Liferay )
Similarly i have my Custom Portlet made of struts2 , which is shown on entering http:localhost:8086 as shown below
<s:form action="helloForm" method="POST" theme="simple">
Enter Your Name:<s:textfield name="namer" />
Enter Your Name:<s:textfield name="passer" />
<s:submit/>
</s:form>
Currently , when user clicks on submit the Struts2 Action class recivies that as shown
public String execute() throws Exception {
HttpServletRequest request = ServletActionContext.getRequest();
String name = ParamUtil.getString(request, "namer");
// does Database check and decides the return page
return ActionSupport.SUCCESS;
}
Now my requirement is that , if he is a valid user , i want to redirect him/her to the Landing Page configured
Please tell me how to do this ??
HttpServletResponse response = ServletActionContext.getResponse();
String targetUrl = "/user/test/home";
response.sendRedirect(targetUrl);

Resources