How to access C# application from jQuery in Razor Pages? - asp.net-core

Environment: ASP.NET Core 6.0, Razor Pages
Authentication:
The users log in, and their access token (Bearer jwt) is stored in Session storage.
The problem:
I want to display an array of objects that a request gives me. These objects have a part that has to be queried separately for each of them from the backend. For presentation purposes, I want to display them on the same page, but I do not want to query them before the page loads, because that is slow.
So the question is, how to access HttpClient (or the service that uses HttpClient and handles requests) from jQuery? My current idea is to pass the Bearer token to the partials, that way it can be accessed from them, and to go with an XMLHttpRequest. Are there any better solutions to populate these dropdowns on demand?
Index.cshtml
#page
#model IndexModel
#foreach (var part in Model.Parts)
{
<partial name="_PartPartial" model="new { PartData = part, Id = #Model.Id, Bearer = Bearer }" />
}
Index.cshtml.cs
public class IndexModel : PageModel
{
public List<PartModel> Parts;
public string Id;
public async Task<IActionResult> OnGetAsync(string Id)
{
// Query Parts from endpoint...
return Page();
}
}
_PartPartial.cshtml
#model dynamic
<div class="card">
<div class="card-header">
<button type="button" class="btn" data-bs-toggle="collapse" data-bs-target="#partContent_#Model.PartData.partId"
aria-expanded="false" aria-controls="partContent_#Model.PartData.partId" data-id="#Model.PartData.partId" onclick="queryInnerData(this);">
#Model.PartData.DisplayName
</button>
</div>
<div class="collapse" id="partContent_#Model.PartData.partId">
<div class="card card-body">
<div id="innerData_#Model.PartData.partId"></div>
</div>
</div>
</div>
<script type="text/javascript">
function queryInnerData(sender)
{
// This is the part where I am stuck. How do I access the Bearer
}
</script>
I have tried creating a page that does an HttpRequest and returns a JsonResult in its OnGet method. This works, but exposes the raw data to the outside world as well, so I would rather not go with this solution.

Related

Post form with a page handler after changing the value in Select input

I am rather new to .NET 6/Razor pages. I want to mimic the behavior of the web forms when the drop down list value was changed. In web form, I could do OnSelectedIndexChanged and it would hit a specific method in the code behind. What is the best way to do this with a razor page?
Currently, I have
<button class="btn btn-info text-white" asp-page-handler="ResetForm"><i class="fa-solid fa-ban"></i> Clear</button>
<select asp-for="CurrentPage" onchange="ddlCurrentPageChange()" asp-items="Model.DdlPages" ></select>
<script>
function ddlCurrentPageChange() {
document.getElementById("form");
}
</script>
The issue is if I click the reset button and then change the DDL value, it posts to the handler ResetForm
You could always make your onchange the submit action, which will POST:
<form method="post" id="test">
<select asp-for=CurrentPage onchange="document.forms['test'].submit();" asp-items=#Model.ddlPages ></select>
</form>
page.cshtml.cs
public void OnPost(string currentPage)
{
MoveTo(currentPage);
}
public void MoveTo(string page)
{
Console.WriteLine(page);
}
EDIT: Fixed incorrect data as pointed out by #jeremycaney

How do I build an Asp.Net Core Razor page control that allows users to sign their names?

I am building an Asp.Net Core web application using Razor.
The intended audience for this app will be using it on tablets.
Part of the application consists of several pages/forms that will require user signatures.
We could retrieve an image of a user's signature and display that on demand in the web page.
Is it possible to be more interactive and allow users to "sign" the form/page within the browser? Are there any 3rd party control libraries that would support this functionality?
I pretty sure this can be done on native applications, but can I achieve this through Asp.Net Core?
I found signature_pad in github, and it works for me.
You can take a look at the screenshots of my test steps first, and I will add the test code at the bottom.
Test Code
1. signature.cshtml
#*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*#
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/signature_pad#2.3.2/dist/signature_pad.min.js"></script>
<form method="POST">
<p>
<canvas width="500" height="400" id="signature"
style="border:1px solid black"></canvas><br>
<button type="button" id="accept"
class="btn btn-primary">
Accept signature
</button>
<button type="submit" id="save"
class="btn btn-primary">
Save
</button><br>
<img width="500" height="400" id="savetarget"
style="border:1px solid black"><br>
<input id="SignatureDataUrl" type="text">
</p>
</form>
<script>
$(function () {
var canvas = document.querySelector('#signature');
var pad = new SignaturePad(canvas);
$('#accept').click(function () {
var data = pad.toDataURL();
$('#savetarget').attr('src', data);
$('#SignatureDataUrl').val(data);
pad.off();
});
$('#save').click(function () {
$.ajax({
url: "/ForTest/get_signature",
type: "POST",
data: { base64png:$('#SignatureDataUrl').val()},
success: function (data) {
console.log("success");
},
error: function (hata, ajaxoptions, throwerror) {
alert("failed");
}
});
});
});
</script>
2. C# code
[HttpPost]
public string get_signature(string base64png) {
var dataUri = base64png;//"data:image/png;base64,iVBORw0K...";
var encodedImage = dataUri.Split(',')[1];
var decodedImage = Convert.FromBase64String(encodedImage);
System.IO.File.WriteAllBytes("signature_pic/"+DateTime.Now.ToString("yyyyMMddHHmmss")+"signature.png", decodedImage);
return "ok";
}
Tips
If you want test my code, you need create signature_pic folder like me.

sitefinity update view from action method

I am using this form to post data to the controller once the data is saved I wanted to display this Success view but I am getting a 404 page not found. Any idea why I am getting this error?
View
#using (Html.BeginFormSitefinity("Save", "Preference", FormMethod.Post, new { id = "preferencesForm" }))
{
<div class="row">
<div class="text-center">
<button type="submit" class="btn btn-primary px-4 float-center">Save</button>
</div>
</div>
controller
public ActionResult Save(PreferenceViewModel model)
{
SaveData(model);
return View("~/Mvc/Views/Preference/Success.cshtml");
}
Does your code hit the Save method? I don't see it marked with the [HttpPost] attribute.
Also, if this is your Preference widget, then you can simply use this:
return View("Success");
Sitefinity will look for success.cshtml in the correct folder.

Navigate to new view on button click in ASP.NET Core 3.1 MVC

I am trying to navigate the user to a new page with a button click. I am having issues with rendering the new view. Any time I do click on the button, I either get an error for a dropdown on my page, or I get the home page view but with my desired route in the URL. I wanted to note that the user will be navigating to this page from the home page, which I made into my new landing page on the app. I wanted to point that out in case something I did here can be modified.
How I created a new landing page
I want to navigate from my landing page to my book inventory page.
My View (On the landing page):
<form method="post" asp-controller="Home" asp-action="Home" role="post">
<div class="form-group">
<label asp-for="bookName"></label>
<select name="bookName" asp-items="#(new SelectList(ViewBag.message, "ID", "bookName"))">
</select>
</div>
<div class="form-group">
<input type="submit" value="Submit" class="btn btn-primary" />
</div>
</form>
<div class="row">
<div class="form-group">
<a asp-controller="BookInventory" asp-action="Index">
<input type="button" value="Book Inventory Page" />
</a>
</div>
</div>
My Controller (On my landing page)
public void GetBooksDDL()
{
List<BookModel> bookName = new List<BookModel>();
bookName = (from b in _context.BookModel select b).ToList();
bookName.Insert(0, new BookModel { ID = 0, bookName = "" });
ViewBag.message = bookName;
}
[HttpGet("[action]")]
[Route("/Home")]
public IActionResult Home()
{
GetBooksDDL()
return View();
}
My Controller (On my book inventory page):
[HttpGet("[action]")]
[Route("/Index")]
public IActionResult Index()
{
return View();
}
I wanted to note that my breakpoint on my book inventory controller does hit the 'return View()', but it will still render the items from the homepage.
The error I get with the book dropdown says:
ArgumentNullException: Value cannot be null. (Parameter 'items')
Microsoft.AspNetCore.Mvc.Rendering.MultiSelectList.ctor(IEnumerable items, string dataValueField, string dataTextField, IEnumerable selectedValues, string dataGroupField).
I'm wondering why I'm getting this error when I'm trying to navigate to a different page. Since this is the new landing page, is it possible that it is passing along all of its data to the rest of the pages?
ArgumentNullException: Value cannot be null. (Parameter 'items')
Microsoft.AspNetCore.Mvc.Rendering.MultiSelectList.ctor(IEnumerable
items, string dataValueField, string dataTextField, IEnumerable
selectedValues, string dataGroupField).
About this error, it means that you didn't set the value for the select element, before return to the view, please check the ViewBag.message value, make sure it contains value.
Note: Please remember to check the post method, if the Http Get and Post method returns the same page, make sure you set the ViewBag.message value in both of the action methods.
I wanted to note that my breakpoint on my book inventory controller
does hit the 'return View()', but it will still render the items from
the homepage.
In the BookInventory Controller Index action method, right click and click the "Go to View" option, make sure you have added the Index view.
Based on your code, I have created a sample using the following code, it seems that everything works well.
Code in the Home Controller:
[HttpGet("[action]")]
[Route("/Home")]
public IActionResult Home()
{
GetBooksDDL();
return View();
}
[HttpPost("[action]")]
[Route("/Home")]
public IActionResult Home(BookModel book, string bookName)
{
GetBooksDDL();
//used to set the default selected value, based on the book id (bookName) to find the book.
List<BookModel> booklist = (List<BookModel>)ViewBag.message;
book = booklist.Find(c => c.ID == Convert.ToInt32(bookName));
return View(book);
}
Code in the Home view:
#model BookModel
#{
ViewData["Title"] = "Home";
}
<h1>Home</h1>
<form method="post" asp-controller="Home" asp-action="Home" role="post">
<div class="form-group">
<label asp-for="bookName"></label>
<select name="bookName" asp-items="#(new SelectList(ViewBag.message, "ID", "bookName", Model == null? 0:Model.ID))">
</select>
</div>
<div class="form-group">
<input type="submit" value="Submit" class="btn btn-primary" />
</div>
</form>
<div class="row">
<div class="form-group">
<a asp-controller="BookInventory" asp-action="Index">
<input type="button" value="Book Inventory Page" />
</a>
</div>
</div>
Code in the BookInventory controller:
public class BookInventoryController : Controller
{
[HttpGet("[action]")]
[Route("/Index")]
// GET: BookInventory
public ActionResult Index()
{
return View();
}
The screenshot as below:
If still not working, please check your routing configuration, perhaps there have some issue in the routing configure.
Your anchor tag formation is incorrect. You cannot write a button within anchor tag.
Do something like this:
<a asp-action="Index" asp-controller="BookInventory" class="btn btn-primary">Book Inventory Page</a>
Here the class will help your anchor tag look like buttons. I hope you have used bootstrap in your project. If not, then use it.
Hope this helps.
Here is another simple way:
<button type="button" class="btn" onclick="window.location.href = '/YourPage'">Button Title</button>

Web API authentication turns request.authentication false

I have a project that I created in ASP.NET MVC and now the second part of the work is pass the logic of the ASP.NET MVC application (basically the database) to an ASP.NET Web API and do the connection with them.
The thing is, I already did the connection and I already save values in the Web API database, but a strange thing is happening.
I have markup in my layout.cs.html file:
<body>
#if (User.Identity.IsAuthenticated)
{
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
#Html.ActionLink("MSDiary", "Index", "Home", new { area = "" }, new { #class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>#Html.ActionLink("Despesas", "Index", "Despesas")</li>
<li>#Html.ActionLink("Rendimentos", "Index", "Rendimentos")</li>
<li>#Html.ActionLink("Tipos de Despesa", "Index", "TipoDespesas")</li>
<li>#Html.ActionLink("Tipos de Pagamento", "Index", "TipoPagamentos")</li>
<li>#Html.ActionLink("Tipos de Rendimento", "Index", "TipoRendimentos")</li>
</ul>
#Html.Partial("_LoginPartial")
</div>
</div>
</div>
}
<div class="container body-content">
#RenderBody()
<footer>
#if (Request.IsAuthenticated)
{
<p>#Html.Action("_ObtemSaldo", "Home")</p>
}
</footer>
</div>
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
That request is authenticated shows the navbar on the top or not depending if the user is authenticated I cut it off to see if in fact my program is getting the user, and it kinda get the user so the problem is not with the connection, but the request is authenticated don't change in the controller maybe :S
Here is my login controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
try
{
var client = WebApiHttpClient.GetClient();
string username = model.Email;
string password = model.Password;
HttpContent content = new StringContent(
"grant_type=password&username=" + username + "&password=" + password,
System.Text.Encoding.UTF8,
"application/x-www-form-urlencoded");
var response = await client.PostAsync("/Token", content);
if (response.IsSuccessStatusCode)
{
TokenResponse tokenResponse =
await response.Content.ReadAsAsync<TokenResponse>();
WebApiHttpClient.storeToken(tokenResponse);
// return Content(tokenResponse.AccessToken);
return RedirectToAction("Index", "Home");
}
else
{
return Content("Ocorreu um erro: " + response.StatusCode);
}
}
catch
{
return Content("Ocorreu um erro.");
}
}
I already tried with user.authenticated still doesn't work if someone can give me a hand I would appreciate it a lot :D
ps: Sorry for my bad English
If I understood, you have an MVC Application and you have passed some logic (database access) to an Web Api project, so when you send a form/request to the server, it will be received by the controller from MVC, and after that the request will be send to the WebApi (at least is what I understood from your code).
Your problem is that the user logs into the application, the MVC Controller goes to the WebApi to authenticate the user and afterwards, even the login and password been correct the MVC (View) still considers that the user is not logged in.
Well, if what I described is correct, seen your code, I would say that the user is indeed being authenticate in the Web Api, however, as the MVC is the direct interface with the user, it is missing set the MVC Application User as authenticated through some authentication method, something like:
FormsAuthentication.SetAuthCookie(username, false);
It´s worth to say that you would have to store the token (from webapi, after user has been authenticated) so that the nexts requests to the WebApi it considers the user authenticated to that specific token.
Hope I´ve helped.
Regards,