Page 1 of 1

ASP.NET - Razor Pages and Core 2. Advanced. 1 of 2

#1 modi123_1   User is online

  • Suitor #2
  • member icon



Reputation: 14353
  • View blog
  • Posts: 57,560
  • Joined: 12-June 08

Post icon  Posted 06 January 2018 - 11:40 AM

Requirements:
Visual Studios 2017 Community or higher.
More than a passing familiarity with C# and SQL

Github:
https://github.com/m...ore2Walkthrough

ASP.NET - Razor Pages, Core 2, and Entity Framework. Basics.
ASP.NET - Razor Pages and Core 2. Advanced. 2 of 2

Welcome to a further exploration of Core 2 ASP.NET with Razor pages. This takes the basic setup, from the previous tutorial, and adds a host of new functionality to make a fairly complex site that hits on most of the bases.

Please start with the 'Basic' tutorial for a further explanation on Razor pages, Core 2, and what the project will look like.

The sign post for what will be expanded is as follows:
- Actual DB implementation
- Pulling 'news' on a page load
- Creating a basic registration system
- Allowing log in
- Conditionally showing certain information based on logged in or not and roles.
- Sessions
- Multiple 'post's on a page, and even one with giving a parameter
- Custom queries that go beyond a simple SELECT ALL.
- Timed actions from the user
- A leader board for ranking.

The idea is to make a boiled down web game. Folk log in, can initiate some action for points, and have to wait until X minutes to be able to do it again. In the mean time there is 'administrative' functionality to add new activities, news, remove users, etc.

Essentially this should provide you with the scaffolding for a fully functional website.

This is a large area to cover so I will hit interesting areas and expect folk to reference the github repo for the full code.

Given the previous project - we left off with an in memory database, but we could shuffle data to and from the table using a class as the database outline. Let's look to change that.

Things to remember:
- DB context is your data henchmen and wing man. If there's the need to move data to a database or what not make sure he's there with you.
- A given page's "model" is any set of properties and variables in its code behind.
- If you want to see any user input, and have it survive the journey, flag the property or variable as [BindProperty]

Tutorial

1. Persistent database data.
To start let's make an actual database for persistent data. It turns out to be a snap to do this and allows a plethora of new functions.

Go to View -> SQL Server Object Explorer, and give it a few seconds to spin up.

You should have a tree view of various databases. Dive into (localdb)\projectsv13, and into the folder called 'databases'. Right click on that folder -> New and call it 'DB_Core2Walkthrough'.

This creates a local MDF in what ever migratory app data location Visual Studios lands on.

Right click on the new database -> properties.

Copy the contents of the 'connection string' property to Notepad doc as we will need it shortly.

From there open up 'startup.cs', focus on 'ConfigureServices', and comment out the 'in memory database' line. Replace it with a db context for Sql Server and our Connection String name.

            //   services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("name"));

            //Important to db linking to actual sql database.
            services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("Core2WalkthroughContext")));



Save that and open up 'appsettings.json'. This file holds all sorts of tricky little application wide settings in the simple json format. No more complex web configs!

Inside the root braces add a "ConnectionStrings: " section, and in there add and entry called 'Core2WalkthroughContext' (see how that matches what we did in the startup.cs!), and paste in your connection string you saved in the Notepad doc.

Here's is what mine looked like:

  "ConnectionStrings": {
    //Important to db linking
    // From the DB's properties when you click on the databse in the 'sql server object explorer'
    "Core2WalkthroughContext": "Data Source=(localdb)\\ProjectsV13;Initial Catalog=DB_Core2Walkthrough;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"

  }



Save it up.

Back in the 'sql server object explorer' drill into our database, right click on 'Tables' -> Add New Table. Call it 'USERS'.

Here we need to mimic the property names from our 'USERS' class in the data folder and their data types.

We are almost ready to fire this up and test to verify functionality.

Right now we have the DB site ready.. and the project ready to connect, but we need that little piece of information to mate our Razor page code with the table. In comes our AppDbContext.CS. Open it up and in the 'OnModelCreating' add a single line to help the project know what database goes to what data class.

            modelBuilder.Entity<USERS>().ToTable("USERS");


Save that and run it. Data should be writing to the persistent DB and when you stop the proejct and start it again the data is still there. Cool!

If you haven't fiddled around with the localdb and SQL server it would be a good time to stop and do so. You can directly add data to the tables, edit what is there, etc.

The steps adding a database table to our local SQL Server database, adding data class, and matching that up the entity to the db will be repeated three more times so make a note to come back to this if you have questions.

2. Expand User DB to more columns.

Let us expand out User to include a few more columns. In the 'USERS.cs' in data add properties for XP, IS_ADMIN, and DESCRIPTION. We will need these later on expanding out various ways to interact with the USER data.

Make corresponding columns in the 'USERS' table in the local sql server. Right click on the table 'Users' -> View Designer and add them at the bottom.

Save it all.

3. Let there be news.
I find with most websites that have user registration and activity from time to time the site needs to communicate to their people. Typically this is a spot on the main page where 'news' is displayed. You certainly could hardcode the news in HTML and redeploy but that is a pain. Instead save the news information into a database table and let the page show the most current!

To start let's make our class. In the 'Data' folder of the solution create a new class called 'NEWS'. There is not much needed at the moment so and ID, text, and a date entered properties is all that should go in there.

Remember - the helper "[Display(Name =" will be used for any labels first.. if not present it will use the variable name. Having it here provides a more user friendly way to give the property a name.

    public class NEWS
    {
        public int ID { get; set; }

        [Display(Name = "Text")]
        public string TEXT { get; set; }

        public DateTime DATE_ENTERED { get; set; }

    }



In the database (SQL Server Object Explorer tab) create a table appropriately named 'NEWS' and three columns that match our class.

Finally go to our AppDBContext.cs -> OnModelCreating, and add a line right below our 'USERS' line to let the solution know to marry the table to the class for us.

modelBuilder.Entity<NEWS>().ToTable("NEWS");


Super. 'News' is now primed and ready to go!

In a bit we will clean up the index to be more presentable, but let's think who should be able to add news and where that may go. Not every user should have the ability to add news.. only administrators to the site. (Recall we added a boolean flag to the USERS table to indicate 'IS_ADMIN'). What is needed is a special page that will will lock down for admins only.

3. Administration page
The Admin page will be a single page where folk who have the right flag, and are logged in, will be able to do all sorts of sweeping changes to the site. Add news, remove news, remove users, add activities, etc. All this functionality will be contained in one spot and help keep the website humming.

For the first part we will be concerned about adding functionality to the page, and later in the tutorial about locking it down.

Start by right clicking on the 'Pages' folder -> add razor page. Give it a title "Admin", and ok through the creation.

Open up the 'code behind' for Admin.cshtml.cs and we will begin to add our familiar options.

At the top add:
- the db context so data can flow to and from the database,
- our message string so we can indicate to admins something was successful,
- a news object for adding news (remember BindProperty means the data survives the voyage back from the client to the server),
- and a list of news to display if we want to delete it.

These parts are now the page's "model", and help shuffle data to and from the page to the code behind.

Side note, if your page needs to move data you will always have a DB context object.

        private readonly AppDbContext _db;// access route to and from the DB.

        [TempData]
        public string Message { get; set; }// no private set b/c we need data back

        [BindProperty] // survive the journey _BACK_ from the user.
        public NEWS NewsAdd { get; set; }// private not set because the data is needed back

        public IList<NEWS> NewsList { get; private set; }// private set so don't need to have data back.. just to show.



Add a constructor and have it set our DB context variable.
        //Constructor
        public AdminModel(AppDbContext db)
        {
            //Get the database context so we can move data to and from the tables.
            _db = db;
        }



The OnGet (when the page loads) gets our database data and sets up our list to show the user.

The line is pretty nifty in compactness. It asks the DB context object to go get the *entire* news data from the database and convert it to a list. The list will be assigned to the page's model and in the HTML we will display it.
        //When the page is being fetched, load some data to be rendered.
        public async Task OnGetAsync()
        {

            NewsList = await _db.NEWS_DBSet.AsNoTracking().ToListAsync();
        }



That should take care of getting the news data from the database.

4.1 Admin - Add news.
Here is where things get a little more tricky: adding and removing the news. Thankfully half the battle is won with the variables declared.

First let's start with adding data.

In the CSHTML of the Admin page add a section for a form post to have a label for 'NewsAdd' 's TEXT to display any name we gave it and an input box. Using the ASP page handler helpers we give the 'submit' button a unique name.

Remember the asp-for in the label will first try and use any "[Display(Name =" added in the NEWS class in the 'Data' folder before just using the property name.
    <h4>Add News</h4>
    <form method="post">
        <div>
            <label asp-for="NewsAdd.TEXT"></label>
            <input asp-for="NewsAdd.TEXT" />
        </div>
        <input asp-page-handler="NewsAdd" type="submit" value="Add News" />
    </form>



Save that, and head back to the code behind.

From the previous tutorial we know that we can have many 'post' methods coming from one form with the 'asp-page-handler' we can decipher which comes from what call.

Start by adding an 'OnPostAsync' method and wedge in the 'NewsAdd' from the asp-page-handler. Yup.. a little black magic on the Razor Page's part knows to match this up.

Start by checking if the model is valid, set the 'date entered' to now, and add the NEWS object to the db context's dataset for NEWS.

Await a 'save chagnes' call, add a nice message to tell the admin save was added, and redirect back to itself.

        //The user signals there is an action to do.  Notice how the words between "OnPost" and "Async" match up with the CHTML's "asp-page-handler"
        public async Task<IActionResult> OnPostNewsAddAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            //Snag the current date time.
            NewsAdd.DATE_ENTERED = DateTime.Now;
            
            //Add it to the News database set.
            _db.NEWS_DBSet.Add(NewsAdd);
            
            //Update it.
            await _db.SaveChangesAsync();

            //Inform the user of much success.
            Message = $"News added!";

            //Send it back to the admin page.
            return RedirectToPage("/Admin");
        }




Bada bing, bada boom. An admin now can save data.

You should be able to run the project and verify text is being added to the Database through directly viewing the data in the database table as outlined above.

4.2 Admin - Remove news.
To remove news I would think the admin would like to know the text and when it was added. Sounds like showing what is in a table, but we can be a little more helpful and add a button on each row for 'delete' with a value of that row's ID from the database. It is way less complicated than it sounds.

Starting with the CSHTML we will use a basic HTML table to show the t ext and the data entered. You could show the ID, but really the admin won't care for it.

Since this will have user action that the page needs to handle AFTER we surround it with a form post.
  <form method="post">
        <table class="table">
            <thead>
                <tr>
                    @*<th>id</th>*@
                    <th>Name</th>
                    <th>Date Added</th>
                </tr>
            </thead>



The body holds more interesting magic.

Using the joy of Core2 in the body utilize a for-each loop to go through the list of news pulled at the load of the page and create rows with <td> for each property in our object.

The 'Core 2 Basics' tutorial outlined the use of the '@' symbol to do this.

Create one third TD of a submit button. We have the typical asp-page-handler to give it a specific function in the code behind, but we also provide a parameter - the id! The magic here is "asp-route-" is required, but "id" could be any parameter name we want, but it must correspond to the parameter for our 'on post async' function in the code behind.

            <tbody>
                @foreach (var temp in Model.NewsList)
                {
                    <tr>
                        @*<td>@temp.ID </td>*@
                        <td>@temp.TEXT</td>
                        <td>@temp.DATE_ENTERED</td>
                        <td>
                            @*Important information here.. matching up the 'asp-page-handler' to the CS function, and the asp-rout- variable name and value.*@
                            <button type="submit" asp-page-handler="DeleteNews" asp-route-id="@temp.ID">Delete</button>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </form>



Save that and head to the code behind. Much like the 'Add News' we need an 'on post async' method.

The method name is from the asp-page-handler="DeleteNews, and the parameter is from the "asp-route-". The value is provided there as well.
        public async Task<IActionResult> OnPostDeleteNewsAsync(int id)



The body is pretty simple. First, use the ID to ask the DB context to find the row in the database. If found flag the row as 'remove', sand save it. The DB context takes care of the nitty gritty.

Once done tell the admin it happened and redirect back to the admin page. (Yes.. another possible way to head bacvk to the same page).
        {
            var temp = await _db.NEWS_DBSet.FindAsync(id);

            if (temp != null)
            {
                _db.NEWS_DBSet.Remove(temp);
                await _db.SaveChangesAsync();

            }

            Message = $"News deleted.";


            return RedirectToPage();
        }



Test it out. You should now be able to see any news data added as well as remove it! Nifty!

5. Add the news to the index.
It is time to clean up index and make the page more akin to what users would expect to see. Head to the index.CSHTML and clear it out. I threw in some basic welcoming message with plain HTML as it looked nice.

Add a div for the news to be shown on page load.

Head to the code behind for the index page.

Since we need data from the database we need the trusty db context and a list of NEWS objects to display. The message string is for future use.

        private readonly AppDbContext _db;

        public IList<NEWS> NewsList { get; private set; }// private set so don't need to have data back.. just to show.

        [TempData]
        public string Message { get; set; }// no private set b/c we need data back



Per usual the constructor for the page must be assigned our DB context.

        //Constructor
        public IndexModel(AppDbContext db)
        {
            //Get the database context so we can move data to and from the tables.
            _db = db;
        }



Finally, when the page is loaded get data from the NEWS table in the database.

        //When the page is being fetched, load some data to be rendered.
        public async Task OnGetAsync()
        {
            NewsList = await _db.NEWS_DBSet.AsNoTracking().ToListAsync();
        }



With our page's model in hand head back to the HMTL. Using some Razor love we display each row of news text and the date.

<div>
    <h3>News</h3>
    <div style="margin-left:20px;">
        @foreach (var temp in Model.NewsList)
        {
            <h4>@temp.DATE_ENTERED.ToShortDateString()</h4>
            <span style="margin-left:20px;">@temp.TEXT</span>
            <br />
        }
    </div>

</div>



Save it, wipe your brow, and test it.

Congratulations - the basic steps here are the bulk of the important concepts you will need to get data to a database, remove data, and view data. Only a bit of black magic goes on with OnPost method names and variables, but the rest is there.

Keep in mind each page's "model" consists of the properties and there are clear lines drawn for each post to each method.

Swallow this down, pat yourself on the back, and get a cookie. You certainly deserve it! It only gets more interesting from here in part 2.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1