How to call method from another Razor page? - asp.net-core

This is structure of my App and i want to call a method in Login.cshtml from ExternalLogins.cshtml.cs class. How can I do that? Some asp helpers?
Login just like ExternalLogins is RazorPage - no controller class.
Edit:
#mj1313 I added this code:
<div class="col">
<h3>Zaloguj się przez:</h3>
#{
if (Model.ExternalLogins.Count == 0)
{
<div>Brak możliwości zalogowania przez serwisy zewnętrzne.</div>
}
else
{
<form method="post"
asp-page="./Manage/ExternalLogins"
asp-page-handler="LinkLogin"
asp-route-returnUrl="#Model.ReturnUrl">
<div>
#foreach (var provider in #Model.ExternalLogins)
{
<button type="submit"
class="btn btn-primary"
name="provider"
value="#provider.Name"
title="Zaloguj się za pomocą konta
#provider.DisplayName">
#provider.DisplayName
</button>
}
</div>
</form>
}
}
</div>
But my Action in ExternalLogins not called
public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}

Related

Is there a way for Code Block feature to keep line breaks in CKEditor5 with ASP.Net Core?

I am making a bulletin board system using CKEditor. Most of the features work just fine, but when editing an existing post, the all line breaks in the text are removed from the code block.
Image of create a post
Image of edit a post
Image of part of the response source
I googled as much as possible to solve this problem, but the methods I found were to no avail, so I removed it from the code again.
It seems that line breaks are removed while processing the source internally in CKEditor5, is there any way?
Replace all line breaks with <br /> tags.
Add /\r|\n/g to protectedSource
The following is the view file for that feature.
#model BBSArticleWriteView
#{
// Action name of the current view
var thisActionString = #ViewContext.RouteData.Values["action"].ToString();
if (Model.ArticleId == null)
ViewData["Title"] = "Writing";
else
ViewData["Title"] = "Editing";
}
<p class="page-header">#ViewData["Title"]</p>
<form asp-action="#thisActionString" id="editor-form">
<input asp-for="ArticleId" value="#Model.ArticleId" hidden />
<div>
<input asp-for="Title" required placeholder="Please enter a title." class="form-control w-100 mb-2" />
</div>
<div>
<textarea name="Contents" id="editor">
#Html.Raw(Model.Contents)
</textarea>
</div>
<div>
<input class="btn btn-sm btn-primary" type="submit" value="Save" onsubmit="Editor.submit()" />
<button class="btn btn-sm btn-primary" type="button" href="##" onclick="history.back()">Back</button>
</div>
</form>
<style>
.ck-editor__editable_inline {
min-height: 400px;
}
</style>
#section Scripts {
<script src="~/lib/ckeditor5/ckeditor.js" asp-append-version="true"></script>
<script>
class Editor{
static submit() {
return true;
}
}
ClassicEditor
.create(document.querySelector('#editor'),
{
simpleUpload:{
uploadUrl: "#Url.Action(nameof(CreatorFront.Controllers.FileController.Upload), "File")",
withCredentials: true
},
protectedSource:[
/\r|\n/g
]
})
.catch(error => {
console.error(error);
});
</script>
}
And here is the controller action that puts data into the view model.
[HttpGet]
public async Task<IActionResult> BBSEdit(int id)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
if(user == null)
{
return RedirectToAction("Index", "Home");
}
var article = _cContext.BBSArticle.First(a => a.ArticleId == id);
if(article == null)
{
return RedirectToAction(nameof(BBSList));
}
if(user.Id != article.UserId)
{
return RedirectToAction(nameof(BBSList));
}
var model = new BBSArticleWriteView();
CopyProperties(model, article);
return View(nameof(BBSWrite), model);
}
The following is a function that puts content data in DB.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> BBSWrite(BBSArticleWriteView article)
{
if(ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
if(user == null)
{
RedirectToAction("Index", "Home");
}
// XSS attacks prevent
article.Contents = _htmlSanitizer.Sanitize(article.Contents);
var currentDateTime = DateTime.Now;
CreatorLib.Models.BBS.BBSArticle data = new CreatorLib.Models.BBS.BBSArticle()
{
ArticleId = _cContext.BBSArticle.Max(a => a.ArticleId) + 1,
MainCategory = article.MainCategory,
SubCategory = article.SubCategory,
UserId = user.Id,
Title = article.Title,
Contents = article.Contents,
Status = CreatorLib.Models.BBS.ArticleStatus.A,
IpAddress = HttpContext.Connection.RemoteIpAddress.ToString(),
RegisteredTime = currentDateTime,
LastUpdatedTime = currentDateTime,
HasMedia = article.HasMedia
};
_cContext.BBSArticle.Add(data);
await _cContext.SaveChangesAsync();
return RedirectToAction(nameof(BBSList));
}
return View(article);
}
Here, it is confirmed that HtmlSanitizer has absolutely no impact on this issue.
In DB, line breaks are fully preserved.

Controller not executing code (Blazor server-side)

My Goal Create a simple login form in order to authenticate and authorize the user in my Blazor Server-side app with Cookie Authentication.
My Issue Everything works... The EditForm passes the values to my Controller. The Controller validates the usercredentials. Then runs HttpContext.SignInAsync(claims) and returns Ok().
But the Cookie is not passed and the user is not Authenticate either.
What I have done
1. The EditForm, passes the userinputs to the DisplayLoginModel() on a ValidSubmit.
<div class="row">
<div class="col-8">
<EditForm Model="#userLogin" OnValidSubmit="OnValidLogin">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="mb-4 row">
<p class="col-sm-4 font-weight-bold">Email</p>
<div class="col-sm-8">
<InputText #bind-Value="userLogin.Email" class="form-control" />
</div>
</div>
<div class="mb-4 row">
<p class="col-sm-4 font-weight-bold">Password</p>
<div class="col-sm-8">
<InputText #bind-Value="userLogin.Password" class="form-control" />
</div>
</div>
<button class="btn btn-outline-primary col-sm-4" type="submit"><strong>Login EditForm</strong></button>
</EditForm>
</div>
</div>
2. The OnValidLogin Sends a request to the Form Controller
public DisplayLoginModel userLogin = new DisplayLoginModel();
private async Task OnValidLogin()
{
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod("POST"),
RequestUri = new Uri("https://localhost:44370/Form"),
Content = JsonContent.Create(userLogin)
};
var client = httpfac.CreateClient();
var response = await client.SendAsync(requestMessage);
}
3. The Controller gets the user credentials from the displayloginModel and validets Ok().
[HttpPost]
public async Task<ActionResult> Post(DisplayLoginModel _userLogin)
{
if (_userLogin.Email == "this#Email.com")
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Role, "ActiveUser")
}, "auth");
ClaimsPrincipal claims = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(claims);
return Ok();
}
else
{
return BadRequest();
}
}
4. Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddHttpContextAccessor();
services.AddAuthentication("Cookies").AddCookie(options =>
{
options.SlidingExpiration = true;
});
services.AddRazorPages();
services.AddServerSideBlazor();
}
And
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
Why is the Controller not signing the user in, what am I missing?
An image of the solution structure is seen right below:
Because Blazor Server uses websockets (Blazor circuits) to render the UI while on the controller, await HttpContext.SignInAsync(claims); returns a cookie header so the browser can be authorized on next page load. This means you are actually being authenticated server-side but not from the client-side as you have not reloaded the page context to update the cookies. There is a reason why the default Identity login uses MVC architecture for the authentication process. :)
My suggestion, switch to API-based authentication/authorization or use a traditional login in flow.

Pass value from one controller to another in ASP.NET Core MVC

How can I pass a dropdown selected value from my view to a controller then to different controller? I have a home page and an about page. On the home page the user selects an item from the dropdown, then they click on a button that navigates them to another page that has the same dropdown. My goal is to have the dropdown prepopulated on the about page based on what they selected on the home page.
My problem is that I am doing the navigation in my home page controller, so I am not getting the value of the selected value because it is not a post just a get. My variable "string countryDDL" in my home controller is null.
I am currently using TempData to pass the value, however, I am open to other options (viewbag, session state, etc). Thank you for your time.
Home page view:
<form method="post" asp-controller="Home" asp-action="Index" role="form">
<div class="form-group">
<label>Country Dropdown:</label>
<select name="countryDDL" asp-items="#(new SelectList(ViewBag.message, "ID", "CountryName"))"></select>
</div>
</form>
<a asp-action="AboutPage" asp-controller="Home">About Page</a>
Home Controller:
public void CountryDDL()
{
List<CountryModel> countryName = new List<CountryModel>();
countryName = (from b in _context.CountryModel select b).ToList();
countryName.Insert(0, new CountryModel { ID = 0, CountryName = "" });
ViewBag.message = countryName;
}
[HttpGet("[action]")]
public IActionResult AboutPage()
{
string countryDDL = HttpContext.Request.Form["countryDDL"];
int intCountry = Convert.ToInt32(countryDDL);
CountryModel data = new CountryModel()
{
ID = intCountry,
CountryName = "",
};
TempData["myData"] = data;
return RedirectToAction("Index", "AboutPage");
}
My Book Page Controller:
[HttpGet("[action]")]
[Route("/Index")]
public async Task<IActionResult> Index()
{
//get values from Home controller
CountryModel data = TempData["myData"] as CountryModel;
return View();
}
Firstly,your code makes a mistake that you could not pass TempData["myData"] redirect to another controller action with the following error message.That is because TempData uses Session, which itself uses IDistributedCache. IDistributedCache doesn't have the capability to accept objects or to serialize objects:
InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type
Here is the whole working demo:
Home/Index.cshtml:
<form method="post" asp-controller="Home" asp-action="Index" role="form">
<div class="form-group">
<label>Country Dropdown:</label>
<select id="sel" name="countryDDL" asp-items="#(new SelectList(ViewBag.message, "ID", "CountryName"))"></select>
</div>
</form>
<a asp-action="AboutPage" asp-controller="Home" >About Page</a>
#section Scripts{
<script>
$("a").click(function () {
var selectItem = $('#sel').find(":selected").val();
var href = $(this).attr('href');
if (href) {
href +="?countryDDL="+selectItem;
$(this).attr('href', href);
console.log(href);
}
});
</script>
}
HomeController:
public class HomeController : Controller
{
private readonly MvcProj3_1Context _context;
public HomeController(MvcProj3_1Context context)
{
_context = context;
}
public IActionResult Index()
{
CountryDDL();
return View();
}
public void CountryDDL()
{
List<CountryModel> countryName = new List<CountryModel>();
countryName = (from b in _context.CountryModel select b).ToList();
countryName.Insert(0, new CountryModel { ID = 0, CountryName = "" });
ViewBag.message = countryName;
}
[HttpGet]
public IActionResult AboutPage(string countryDDL)
{
int intCountry = Convert.ToInt32(countryDDL);
List<CountryModel> data = new List<CountryModel>()
{
new CountryModel()
{
ID = intCountry,
CountryName = "asd",
}
};
TempData["myData"] = JsonSerializer.Serialize(data);
return RedirectToAction("Index", "AboutPage");
}
}
AboutPage/Index.cshtml:
<form>
<div class="form-group">
<label>Country Dropdown:</label>
<select id="sel" name="countryDDL" asp-items="#(new SelectList(ViewBag.message, "ID", "CountryName"))"></select>
</div>
</form>
AboutPageController:
public class AboutPageController : Controller
{
[HttpGet]
public async Task<IActionResult> Index()
{
//get values from Home controller
ViewBag.message = JsonSerializer.Deserialize<List<CountryModel>>(TempData["myData"] as string);
return View();
}
}
Result:
Update
You could get the data like below:
[HttpGet]
public IActionResult AboutPage(string countryDDL)
{
int intCountry = Convert.ToInt32(countryDDL);
//change this line...
var data = _context.CountryModel.Where(c => c.ID == intCountry).ToList();
TempData["myData"] = JsonSerializer.Serialize(data);
return RedirectToAction("Index", "AboutPage");
}
1-you can use form to navigate and submit it with get to about page:
<form method="get" asp-controller="Home" asp-action="AboutPage" role="form">
<div class="form-group">
<label>Country Dropdown:</label>
<select name="countryDDL" asp-items="#(new SelectList(ViewBag.message, "ID", "CountryName"))"></select>
</div>
<button type="submit">About Page</button>
</form>
2-you can use jquery like below:
a: change a tag like this:
About Page
b: and select like below:(set "onChange" event)
<select onchange="$('#a_about').prop('href','/Home/AboutPage?countryDDL='+$(this).val())" name="countryDDL" asp-items="#(new SelectList(ViewBag.message, "ID", "CountryName"))" ></select>

Any Idea how to handle file input fields when updating a form

I am using asp.net core 3.1 with Razor forms.
I have a form that contains an input of type file and it is multiple files input.
In the create form it is easy to access the file from the model.
The problem is in the update form how can handle the preview, delete adding new files to the multiple
file input.
Is there a best practice to solve such thing.
The problem is in the update form how can handle the preview, delete adding new files to the multiple file input. Is there a best practice to solve such thing.
I suggest that you could use jQuery MultiFile.
Here are the steps:
1.Download the jQuery MultiFile:https://multifile.fyneworks.com/#Install
2.Find the download zip file and extract it then move to the project wwwroot/lib folder:
For asp.net core mvc:
View:
<form asp-controller="Home" asp-action="UploadData" enctype="multipart/form-data">
<div>
<input type="file" name="files" multiple="multiple" class="multi with-preview" />
<input type="submit" value="Upload" />
</div>
</form>
#section Scripts
{
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" type="text/javascript" language="javascript"></script>
<script src="~/lib/multifile-master/jquery.MultiFile.js"></script>
}
Controller:
[HttpPost]
public IActionResult UploadData(List<IFormFile> files)
{
//do your stuff...
return Ok();
}
For asp.net core razor pages:
Index.cshtml:
#page
#model IndexModel
<form asp-page-handler="UploadData" enctype="multipart/form-data">
<div>
<input type="file" name="files" multiple="multiple" class="multi with-preview" />
<input type="submit" value="Upload" />
</div>
</form>
#section Scripts
{
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" type="text/javascript" language="javascript"></script>
<script src="~/lib/multifile-master/jquery.MultiFile.js"></script>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
public IActionResult OnGet()
{
return Page();
}
public IActionResult OnPostUploadData(List<IFormFile> files)
{
return Page();
}
}
Result:
I've been using bootstrap4 file input!
to load the images when updating the form I used the following way:
var filesArray = [];
$(document).ready(function ()
{
$("#photos").fileinput("refresh",
{
showUpload: false,
showRemove: false
});
loadPhotos();
setTimeout(function ()
{
if (filesArray.length > 0)
{
$(".file-drop-zone-title").remove();
$('#photos').fileinput('readFiles', filesArray);
}
}, 2500);
});
function loadPhotos()
{
//hddPhotos is a hidden input that I stored the images URLs in
var photosPath = $('#hddPhotos').val();
if (photosPath !== null && photosPath!=='')
{
var photos = jQuery.parseJSON($('#hddPhotos').val());
if (photos.length > 0)
{
var count = photos.length;
for (var i = 0; i < count; i++)
{
getBlobofImage(photos[i]);
}
}
}
}
function getBlobofImage(imagePath)
{
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", imagePath);
xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
xhr.onload = function ()
{
blob = xhr.response;//xhr.response is now a blob object
filesArray.push(new File([blob], /[^/]*$/.exec(imagePath)[0]));
};
xhr.send();
}

Creating dynamic view from a controller in MVC

I have this controller and view:
public ActionResult DynamicView()
{
return View();
}
_
#model ChatProj.Models.GroupNumber
#{
ViewBag.Title = "DynamicView";
}
<h2>DynamicView</h2>
<fieldset>
<legend>Create a room</legend>
<div class="editor-label">
#Html.LabelFor(model => model.GroupId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GroupId)
#Html.ValidationMessageFor(model => model.GroupId)
</div>
<input type="submit" value="DynamicView" />
</fieldset>
This is what it looks like on the page.
That's fine and dandy, but I would like to pass that number to a controller, which then passes it to a view. I would like to pass it to this view:
#using PagedList.Mvc;
#using ChatProj.App_Code;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Grupprum 1";
}
<h2>Grupprum 1</h2>
<style>
ul {list-style-type:circle;}
</style>
<div class="container">
<div class="nano chat">
<div class="content">
<ul id="discussion">
</ul>
</div>
</div>
<input type="text" id="message" />
<input type="button" id="sendmessage" value="Send" disabled="disabled" />
<input type="hidden" id="displayname" />
</div>
#section scripts {
<!--Script references. -->
<!--The jQuery library is required and is referenced by default in _Layout.cshtml. -->
<!--Reference the SignalR library. -->
<script src="~/Scripts/jquery.signalR-1.1.3.js"></script>
<script src="~/Scripts/jquery.nanoscroller.min.js"></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="~/signalr/hubs"></script>
<!--SignalR script to update the chat page and send messages.-->
<script>
$(function () {
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
$(".nano").nanoScroller();
// Create a function that the hub can call back to display messages.
chat.client.addNewMessageToPage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(message) + '</li>');
};
$(document).ready(function () {
$("#sendmessage").removeAttr("disabled");
$('#message').keypress(function (e) {
if (e.keyCode == 13)
$('#sendmessage').click();
});
});
// Get the user name and store it to prepend to messages.
// Set initial focus to message input box.
$('#message').focus();
$.connection.hub.qs = { "room": "Grupprum 1" };
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.send($('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
});
// This optional function html-encodes messages for display in the page.
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
</script>
}
Specifically I would want it at $.connection.hub.qs = { "room": "Grupprum 1" }; to replace the 1.
So I've created these controllers which are faulty and incomplete:
[HttpPost]
public ActionResult DynamicView(int? roomNumber)
{
return View(GroupRoom(roomNumber));
}
public ActionResult GroupRoom(int roomNumber)
{
return View();
}
Does anyone know how I should change my controllers and views so that I'm able to insert a number in my DynamicGroup view, and get a view back based on the inserted number and the lastly mentioned view?
You could pass the number from the model to the new action just how #Matt Bodily did. But if you want to use a different model on your new view, you can use the below code instead:
public ActionResult GroupRoom(int roomNumber)
{
ViewBag.RoomNumber = roomNumber;
return View();
}
This way, you can use a different model for this page, if you want to. To display this ViewBag on the page, use this code anywhere you want:
#ViewBag.RoomNumber
I hope that helps you out.
How you have it set up the Model.GroupID will be set on the first view so change your controller like this
[HttpPost]
public ActionResult DynamicView(GroupNumber model)
{
//model.GroupId here will be what was selected on the first view
return RedirectToAction("GroupRoom", "Controller", new { GroupId = model.GroupId });
}
public ActionResult GroupRoom(int GroupId)
{
var model = //build your model based on the selected GroupId
return View(model);
}