Saving post from textarea to database in Razor pages WebApplication - c#

I'm trying to build a social network on a ASP.NET Core Web Application in Visual Studio.
There is membership and a wall, where members can make posts.
I'm using Tinymce for a textarea with editing tools, and I'm supposed to save the text in the textarea in HTML form to my local Visual Studio database, along with the UserId of the connected user who is posting.
When I run the application and try to post, neither of these seem to work. I'm getting a null entry in the database for both the Text and the UserId.
While UserManager works properly on the .cshtml file and does return the UserId, I can't get it to work on the .cs file
Related code so far, of the .cshtml file:
#using Microsoft.AspNetCore.Identity
#inject UserManager<ApplicationUser> UserManager
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<form asp-route-returnUrl="#Model.ReturnUrl" method="post" enctype="multipart/form-data" asp-controller="UploadFiles" asp-action="Index">
<div class="form-group">
<p>You are connected and can post as #UserManager.GetUserId(User)</p>
<textarea name="txt" id="mytextarea" rows="2" cols="80" asp-for="Posts.Text" >
</textarea>
</div>
<br />
<div class="form-group">
<input type="submit" value="Post" class="btn btn-default" />
</div>
</form>
Related code of the .cs file:
private readonly proj.Data.ApplicationDbContext _context;
public IndexModel(proj.Data.ApplicationDbContext context)
{
_context = context;
}
public Models.Posts Posts { get; set; }
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
UserManager<Data.ApplicationUser> UserManager;
var post = new Models.Posts {UserId = UserManager.GetUserId(User), Text = Posts.Text };
_context.Posts.Add(post);
await _context.SaveChangesAsync();
return Page();
}
I get an "unassigned local variable" error for UserManager.
Can you please help me get the content of the textarea correctly, and define UserManager properly?

You seem to have used Depend injection so you can try constructor injection
private readonly proj.Data.ApplicationDbContext _context;
private UserManager<Data.ApplicationUser> _userMannger;
public IndexModel(proj.Data.ApplicationDbContext context,
UserManager<Data.ApplicationUser> user)
{
_userMannger= user;
_context = context;
}
public Models.Posts Posts { get; set; }
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
var post = new Models.Posts {UserId = _userMannger.GetUserId(User), Text = Posts.Text };
_context.Posts.Add(post);
await _context.SaveChangesAsync();
return Page();
}

I figured out the textarea problem.
The line should be like this instead:
<textarea asp-for="Posts.Text" id="mytextarea" rows="2" cols="80" class="form-control"></textarea>
I forgot to include class="form-control" and name="txt" was somehow causing a problem and needed to be omitted.

Related

Blazor Server side, ExternalRegister buttons at .razor page

Is possible to have the buttons "External Registration" placed inside .razor page (server side)?
The below code is from ExternalRegister.cshtml but I would like to have that two registration buttons (Google, Facebook) as part of the Start.razor page. Is that possible?
#model Aplication.Areas.Identity.Pages.Account.ExternalRegisterModel
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="#Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
#foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="#provider.Name" title="Log in using your #provider.DisplayName account">#provider.DisplayName</button>
}
</p>
</div>
</form>
Yes, it is possible to have the buttons in your razor page.
Of course, to do this, you need to be able to enumerate the available providers, which means you need to pass them in to your Blazor application from
_Host.cshtml (or wherever you host the Blazor application)
note: you cannot pass a list of AuthenticationScheme because .NET will not serialise them, which is why I transform them into a DTO ExternalProvider
#inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
#inject SignInManager<IdentityUser> _signInManager
#{
var state = new InitialApplicationState
{
XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken,
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.ToList()
.Select(scheme=>
new ExternalProvider {Name=scheme.Name, DisplayName=scheme.DisplayName}
)
};
}
<component type="typeof(App)" param-InitialState="state" render-mode="ServerPrerendered" />
The InitialApplicationState and ExternalProvider are simple DTO classes
public class InitialApplicationState
{
public string XsrfToken { get; set; }
public IEnumerable<ExternalProvider> ExternalLogins { get; set; }
}
public class ExternalProvider
{
public string Name { get; set; }
public string DisplayName { get; set; }
}
Now, you need to receive this data in your Blazor code as a Parameter on the App.razor component
#inject InitialApplicationState InitialStateService
#code {
[Parameter] public InitialApplicationState InitialState { get; set; } = default;
protected override Task OnInitializedAsync()
{
InitialStateService.XsrfToken = InitialState.XsrfToken;
InitialStateService.ExternalLogins = InitialState.ExternalLogins;
return base.OnInitializedAsync();
}
}
All we are doing here is declaring the Parameter InitialState that will receive our InitialApplicationState - and then we store that state in a service InitialStateService which is configured in startup.cs as a Scoped dependency.
builder.Services.AddScoped<InitialApplicationState>();
Now, we have a service in our DI container for Blazor that contains a list of available external authentication providers and our forgery protection token.
We can inject the InitialApplicationState anywhere we need it in Blazor e.g. Index.razor and enumerate the ExternalLogins to render buttons
The form is declared slightly differently in Blazor as we don't have the asp* directives:
#inject InitialApplicationState InitialStateService
<form id="external-account"
action="/Identity/Account/ExternalLogin"
method="post">
<div>
<p>
#foreach (var provider in InitialStateService.ExternalLogins)
{
<button type="submit"
name="provider"
value="#provider.Name"
title="Log in using your #provider.DisplayName account">
#provider.DisplayName
</button>
}
</p>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="#InitialStateService.XsrfToken">
</form>
I think the best strategy is to define two OnPost method in your Razor PageModel (Code-Behind). For example:
public void OnPostFaceBook(ExternalLogin provider)
{
//your code here
}
public void OnPostGoogle(ExternalLogin provider)
{
//your code here
}
And in your .cshtml file place two separate form for each one, and add parameter
asp-page-handler
to each submit button. For example:
<button type="submit" class="btn btn-primary" value="FaceBook" value="FaceBook" asp-page-handler="FaceBook">Log in using your FaceBook account</button>
and in other form:
<button type="submit" class="btn btn-primary" value="Google" value="Google" asp-page-handler="Google">Log in using your Google account</button>

Why is my HttpPost method not firing from my Asp.Net Core Razor page?

New to Asp.Net Core Razor here.
I'm trying to hook up a simple button to save on my Razor page.
My [HttpPost]SaveDocument() method is not firing on the the "Save" button click.
Instead, the Index() method is firing.
I'm not sure what I've got hooked up incorrectly.
Here is a snippet of my Razor Page definition:
#model SampleViewModel
#{ Layout = "_DevExtremeLayout";
ViewBag.Title = "Sampe Title";}
<form method="post">
<div id="InputFormPanel" class="InputFormPanel">
<partial name="#Model.ChildViewName" model="#Model.ChildViewModel" />
</div>
<div class="row">
<div class="col-md-3">
<input id="ApprovedBy" class="form-control" asp-for="ApprovedBy" />
</div>
<div class="col-md-2">
<input class="form-control" asp-for="Title" />
</div>
</div>
<button id="SaveButton" type="submit" >Save Form</button>
</form>
Here is my model:
public class SampleViewModel
{
public object ChildViewModel { get; set; }
public string ChildViewName { get; set; }
public string ApprovedBy { get; set; }
public string Title { get; set; }
}
Here is a snippet of my controller:
public class SampleController : Controller
{
public async Task<IActionResult> Index(Guid documentTypeId)
{
SampleViewModel viewModel = new SampleViewModel()
{
};
return View(viewModel);
}
[HttpPost]
public async Task<IActionResult> SaveDocument(SampleViewModel sampleViewModel)
{
return RedirectToAction("Index");
}
}
Being new to this, I imagine I'm doing something foolish here/missing something.
What am I doing wrong here?
If you don't assign the action attribute on an HTML form it will post the request to the same page/route you're currently on, which in this case is your Index action.
So if you add an action attribute like so:
<form method="post" action="SaveDocument">
It should start working. Alternatively you can also rename the SaveDocument method to Index, there won't be any errors since the two method signature won't match. From a code organization point of view that's typically my preference since it makes it clear that the GET/POST actions are for the same page.

Add multiple form entries to List<T> ASP.NET Core

I currently have a form in ASP.NET Core that I'm needing to store the Form data somehow before submitting all the data at once to the database. Submitting the data per addition to the entries renders that entry as "incomplete" and may cause issues internally. I've decided that using List<T> might accomplish my goal. I have written the C# portion that will add my form entries, run my checks and add them to my database as needed. However, what I have written currently only works if entries are entered one at a time so I am trying to create a more dynamic solution to allow multiple entries to be stored prior to submitting to the database.
My test model:
public class TestModel
{
public int Id { get; set;}
[Display(Name = "Account Number")]
public string AccountNumber { get; set;}
[DataType(DataType.Text)]
[StringLength(50, MinimumLength = 3)]
[RegularExpression(#"^[a-zA-Z0-9""/'\s-]*$")]
[Display(Name = "Account Description")]
public string AccountDescription { get; set; }
[Display(Name = "Post Date")]
[DataType(DataType.DateTime)]
public DateTime PostDate { get; set; } = DateTime.Now;
// This is definitely not a good place to put this.
// Tried having this here to have it transfer back and forth between View and Controller
public List<TestModel> TicketList { get; set; } = new List<TestModel>();
}
My test View:
#model TestModel
#{
ViewData["Title"] = "Test Form";
Layout = "_Layout";
}
<h1>#ViewData["Title"]</h1>
<hr/>
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input type="hidden" asp-for="AccountNumber" id="AccountNumber" value="" />
<input type="hidden" asp-for="AccountDescription" id="AccountDescription" value="" />
</div>
<div class="form-group">
<label class="control-label">Account</label>
<select id="AccountInfo" class="form-control" asp-items="ViewBag.AccountNumber" onchange="handleAccountDropDownChange('')">
<option value="">--Select Account--</option>
</select>
<span asp-validation-for="AccountNumber" class="text-danger" />
</div>
<div class="form-group">
<label class="control-label"></label>
<input id="PostDate" asp-for="PostDate" class="form-control" value="#DateTime.Now" />
<span asp-validation-for="PostDate" class="text-danger"></span>
</div>
<div class="form-group">
<table>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="Create" />
</td>
</tr>
<tr>
<td>
<button id="AddFormEntry" type="button" class="btn btn-primary\">Add To Group Entry</button>
</td>
</tr>
</table>
</div>
</form>
</div>
</div>
My test Controller:
[Area("Test")]
public class TestEntriesController : Controller
{
private readonly TestContext _context;
// List to store each entry to be added into the context
List<TestFinalModel> entryList = new List<TestFinalModel>();
// Test Entry Submission model to hold information
TestModel testSubmission = new TestModel();
public TestEntriesController(TestEntriesContext context)
{
_context = context;
}
// GET: Test/TestEntries/Create
public IActionResult Create()
{
// Populate ViewBag for dropdown list
PopulateAccountInfoDropdownList();
return View();
}
// POST: Test/TestEntries/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("AccountNumber, AccountDescription, PostDate")] TestModel entrySubmission)
{
if (ModelState.IsValid)
{
// Create new instance of tools and pass current context
TestEntriesTools tools = new TestEntriesTools(_context);
// Run check on entry Posting descriptions and apply change if needed
entrySubmission = tools.CheckEntryDescription(entrySubmission);
// Add new entry to submission
testSubmission.entryList.Add(entrySubmission);
foreach (var entry in testSubmission.entryList)
{
// Add entry submission data to entryList
tools.CreateTestEntryList(User, entry, entryList);
}
foreach (TestFinalModel item in entryList)
{
item.Id = 0;
_context.Add(item);
await _context.SaveChangesAsync();
}
// Clear out list after entrys have been added to the database
testSubmission.entryList.Clear();
entryList.Clear();
return RedirectToAction(nameof(Index));
}
PopulateAccountInfoDropdownList();
return View(testSubmission);
}
[HttpPost("/Test/TestEntries/Controller/AddToGroupEntry")]
[ValidateAntiForgeryToken]
public void AddToGroupEntry(string accountNumber, string accountDescription, DateTime postDate)
{
TestModel newSubmission = new TestModel();
newSubmission.AccountNumber = accountNumber;
newSubmission.AccountDescription = accountDescription;
newSubmission.PostDate = postDate;
testSubmission.entryList.Add(newSubmission);
}
}
I'm currently sending my data and firing off my AddToGroupEntry() method using an AJAX request with a click event on my #AddFormEntry button. When I debug step-through the process, I see everything is working as intended, entries are getting added, all is good. However, I realize that it is leaving the Scope of the controller to return back to my View to allow for more entries. This causes my global variables to no longer matter, as they don't hold their values due to leaving Scope.
My question:
How can I store multiple form entries in a List<T> without submitting the changes to my database?
I have found that I can send my data via ViewBag[] to my View, however I'm not certain how I would get it back to my Controller or move the data back and forth as needed.
Any help is appreciated! I've still learning the ropes of ASP.NET Core and have only created C# WinForms in the passed. Any help or advice on what I could do better is always appreciated!
Thank you in advance!
You are thinking about it the wrong way.
MVC controllers try to be stateless. Which means you cannot store any information in the controller variables and expect it to be there next time when you go back-and-forth from your view to the controller.
This is because every time you hit a controller action (from the view) you get a new controller. Then when you go back to the view, the controller is destroyed.
So to temporarily save some variables you should store them somewhere else. Like on the filesystem, in a database.
In your case maybe it is best to refactor the code so that you store the entries in a javascript array before sending them all together to the server for processing.

Not returning a list of data into index view in ASP.NET Core MVC

I was programming my own website using ASP.NET Core MVC and following one of the video tutorials I wanted to iterate data which is populated from my database to the view. Here are the codes:
Repository
public class ChoreBearRepository : IChoreBearRepo
{
private readonly DatabaseContext _context;
public ChoreBearRepository(DatabaseContext context)
{
_context = context;
}
/*
This methods returns all categories from the database using the background thread.
*/
public async Task<List<CategoryofTask>> GetAllCategories()
{
return await _context.categoryOfTasks
.Select(category => new CategoryofTask(){
CategoryName = category.CategoryName,
CategoryDesc = category.CategoryDesc,
CategoryID = category.CategoryID,
CategoryImageURL = category.CategoryImageURL,
CategoryRate = category.CategoryRate
}).ToListAsync();
}
}
Controller:
public class HomeController: Controller
{
private readonly IChoreBearRepo _repository;
public HomeController(IChoreBearRepo repository)
{
_repository = repository;
}
// public IActionResult Index()
// {
// return View();
// }
[HttpGet]
public async Task<IActionResult> Index()
{
var data = await _repository.GetAllCategories();
return View(data);
}
}
Index.cshtml
#page
#model IEnumerable<ChoreBear_Website.Models.CategoryofTask>
<div class="row">
#foreach (var category in Model)
{
<partial name="Categories" model=#Model/>
}
</div>
Partial View (Categories.cshtml)
#model CategoryofTask
<div class="card" style="width: 18rem;">
<img class="card-img-top" src="#Model.CategoryImageURL" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">#Model.CategoryName</h5>
<p class="card-text">#Model.CategoryDesc</p>
Go somewhere
</div>
</div>
The list contains data from the database but the website project returns me an error which is :
NullReferenceException: Object reference not set to an instance of an
object. MyApp.Namespace.Home.Views_Home_Index.get_Model()
I do know that the list is null which is why I would like to know where should I initialise the list. I followed the code example from the video tutorial.
PS: I followed this video tutorial Get List of Data from Database
Your code looks OK to retrieve a list and return it to a VIEW "Index", even if that list is empty because there are no records in the database.
However, there are two problems. One your Index.cshtml has the "#page" directive which means it is now a Razor Page instead of a View and expects to find the model in the page behind Index.cshtml.cs or in a #code section on the same page. This is the source of your error.
Additionally, in the following, your are passing the IEnumerable as the model to the partial view, which is expecting an individual CategoryOfTask ...
#model IEnumerable<ChoreBear_Website.Models.CategoryofTask>
<div class="row">
#foreach (var category in Model)
{
<partial name="Categories" model=#Model/>
}
</div>
Try changing ...
<partial name="Categories" model=#Model/>
to ...
<partial name="Categories" model=#category/>
To understand differences between Razor Views and Pages, see this link:
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-5.0&tabs=visual-studio
The preceding code looks a lot like a Razor view file used in an
ASP.NET Core app with controllers and views. What makes it different
is the #page directive. #page makes the file into an MVC action -
which means that it handles requests directly, without going through a
controller. #page must be the first Razor directive on a page. #page
affects the behavior of other Razor constructs. Razor Pages file names
have a .cshtml suffix.

How do I start a ASP.NET Core Razor application from scratch

I am trying to get a clear mental picture on how an ASP.NET Core Razor application works. So I created an empty project template and tried to recreate the original "Razor Pages Template". After learning a lot of things the hard way, It seems just impossible to get a submit button to work. Take a look at some of my code:
Create.cshtml
<p>#Model.Message</p>
<h2>Create a new customer.</h2>
<p>Enter you name.</p>
<div asp-validation-summary="All"></div>
<form method="post">
<div>Name: <input asp-for="Customer.Name" /> </div>
<input type="submit" />
</form>
Create.cshtml.cs
public class CreateModel : PageModel
{
public AppDbContext DB { get; }
[BindProperty]
public string Message { get; set; }
public CreateModel(AppDbContext db)
{
DB = db;
}
[BindProperty]
public Customer Customer { get; set; }
public void OnGet()
{
Message = "This is from the get method";
}
public IActionResult OnPost()
{
Message = "This is from the post method";
if (!ModelState.IsValid)
return Page();
DB.Customers.Add(Customer);
DB.SaveChanges();
return RedirectToPage("/Index");
}
}
Most of that code is from (MSDN Docs), It works if I create a Razor Template and then delete, and arrange everything to my taste. But when I create a project without the template, the button brings up a 404 error. Also, commands like asp-validation-summary are not made bold like they normally are when I'm using the razor pages template.
If you want to use Razor pages, you have to include a #page directive on the top of your page, otherwise it returns 404 Not Found.
#page
#model CreateModel
<p>#Model.Message</p>
<h2>Create a new customer.</h2>
...

Categories