Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Intermediate · 30 minutes

A Crowdfunding DApp on the Algorand Network

This tutorial teaches you how to:

  • Create Web Applications using the C#/.NET Framework
  • Install and use the Algorand Package in your application
  • Add interactivity between your application and the Algorand network to perform transactions
  • Create a Crowdfunding DApp on the Algorand network

Requirements

Background

This is a Crowdfunding DApp where users can create campaigns and Crowdfund their ideas, projects or businesses using the Algorand protocol.
Additional Resources on Crowdfunding:
https://developer.algorand.org/solutions/example-crowdfunding-stateful-smart-contract-application/
https://developer.algorand.org/solutions/creating-crowdfunding-application-algorand-blockchain/

Steps

1. Create a .NET Core Web Application

EditorImages/2021/03/16 19:22/2021-03-16_20_06_58-.png
Fig 1-1
After the step above in Fig 1-1, you will be prompted to select a project. Name it AlgorandCrowdfund

In the next phase, you will be asked to select the type of NET Core Web Application you want to create as shown in Fig 1-2 below:
EditorImages/2021/03/16 19:23/2021-03-16_20_13_48-.png
Fig 1-2

2. Install the Algorand Package

After successfully creating the Web Application Project, install the Algorand SDK to the Application as shown in the image below using the Package Manager:
EditorImages/2021/03/16 19:24/2021-03-16_20_16_02-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
Fig 2-1

3. User Account (Registration/Login)

  • Step 1: Create Application User Class
    EditorImages/2021/03/16 19:27/2021-03-16_20_26_22-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
    Fig 3-1

Make folder called Models and create a class called ApplicationUser.cs in the Models folder shown in Fig 3-1 and add the following code:

using Microsoft.AspNetCore.Identity;

namespace AlgorandCrowdfund.Models
{
    public class ApplicationUser : IdentityUser
    {
        public string AccountAddress { get; set; }
        public string Key { get; set; }
    }
}

  • Step 2: Configure the Startup.cs class as shown in the code snippet below:
    Add the following namespace to qualify the use of ApplicationUser in the Startup.cs class

using AlgorandCrowdfund.Models;

Update the ConfigureServices method as shown below:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            services.AddControllersWithViews();
            services.AddRazorPages();
        }

EditorImages/2021/03/16 19:33/2021-02-26_20_29_34-Window.png
Fig 3-2

  • Step 3: Configure the Identity Pages for User Registration and Login Functionalities
    Right Click on the Areas folder, Click on “Add New Scafolded Item”.
    Proceed to the next step as shown in the image below:
    EditorImages/2021/03/16 19:36/2021-02-27_13_37_17-Window.png
    Fig 3-3

EditorImages/2021/03/16 19:37/2021-02-27_13_42_31-Window.png
Fig 3-4

  • Step 4: Update the Registration.cs located inside Areas\Identity\Pages\Account\Register.cshtml.cs from the newly scaffolded item as shown below:
    EditorImages/2021/03/16 19:38/2021-02-27_13_57_38-Window.png
    Fig 3-5

Update the user variable from
var user = new Identity { UserName = Input.Email, Email = Input.Email };
to
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, Key = key, AccountAddress = address };
This will automatically generate a new Account Address and a key when registering.
Full OnPostAsync Method for Registration:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            if (ModelState.IsValid)
            {
                Algorand.Account account = new Algorand.Account();
                var key = account.ToMnemonic();
                var address = account.Address.ToString();
                var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, Key = key, AccountAddress = address };
                var result = await _userManager.CreateAsync(user, Input.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password.");

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                    var callbackUrl = Url.Page(
                        "/Account/ConfirmEmail",
                        pageHandler: null,
                        values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
                        protocol: Request.Scheme);


                    if (_userManager.Options.SignIn.RequireConfirmedAccount)
                    {
                        return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
                    }
                    else
                    {
                        await _signInManager.SignInAsync(user, isPersistent: false);
                        return LocalRedirect(returnUrl);
                    }
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

            // If we got this far, something failed, redisplay form
            return Page();
        }

  • Step 5: Update the dependency Injection
    Update the Dependency Injection in every Controller associated with
    Login/Registration processes as shown in the image below:
    Replace IdentityUser with ApplicationUser as shown in the Image below:
    EditorImages/2021/03/16 19:40/2021-02-27_14_13_38-Window.png
    Fig 3-6

Close all files, and open all code behind files in controllers.
Use the find and replace as shown below to repeat the step for all other controllers:
EditorImages/2021/04/13 15:55/2021-04-13_16_53_56-Window.png
Fig 3-7

4. Create the Models

  • Step 1: Create two additional models, Funders and RequestFunds as shown in Fig 4-1 below
    EditorImages/2021/03/16 20:53/2021-03-16_21_50_36-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
    Fig 4-1

  • Step 2: Configure the models
    Funders.cs class:

using System.ComponentModel.DataAnnotations;

namespace AlgorandCrowdfund.Models
{
    public class Funders
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public int Amount { get; set; }
        public string Key { get; set; }
        public string Receiver { get; set; }
        public virtual ApplicationUser User { get; set; }
        public int RequestFundsId { get; set; }
        public RequestFunds RequestFunds { get; set; }
    }
}

RequestFunds.cs class:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace AlgorandCrowdfund.Models
{
    public class RequestFunds
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string FundTitle { get; set; }
        [Required]
        public string FundDescription { get; set; }
        [Required]
        public int AmountNeeded { get; set; }
        [Required]
        public DateTime Created { get; set; }
        public virtual ApplicationUser User { get; set; }
        public virtual IEnumerable<Funders> Funders { get; set; }
    }
}

5. Setup the Application Database

Process by updating the ApplicationDbContext class located inside the Data Folder as shown below:
EditorImages/2021/04/13 15:35/2021-04-13_16_31_52-Window.png
Fig 5-1
See the code snippet below:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AlgorandCrowdfund.Models;

namespace AlgorandCrowdfund.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
        public DbSet<AlgorandCrowdfund.Models.RequestFunds> RequestFunds { get; set; }
        public DbSet<AlgorandCrowdfund.Models.Funders> Funders { get; set; }
    }
}

6. Create the Controllers

Funders
Navigate to the controllers folder and right click, click on add a controller and select the options in the images below:
EditorImages/2021/03/16 20:59/2021-02-26_20_51_13-Window.png
Fig 6-1
Select the option highlighted in Fig 6-1 above to create a controller scaffold.
Select the funders class for the Model Class of the controller as shown below in Fig 6-2:
EditorImages/2021/04/13 15:46/2021-04-13_16_45_48-Window.png
Select the options above to create a controller for the funders model.
Update the controller with the code snippet below after successfully creating the controller:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AlgorandCrowdfund.Data;
using AlgorandCrowdfund.Models;
using Algorand;
using Account = Algorand.Account;
using Algorand.V2;
using Algorand.Client;
using Algorand.V2.Model;
using Microsoft.AspNetCore.Identity;

namespace AlgorandCrowdfund.Controllers
{
    public class FundersController : Controller
    {
        private readonly ApplicationDbContext _context;
        private static UserManager<ApplicationUser> _userManager;

        public FundersController(ApplicationDbContext context,
            UserManager<ApplicationUser> userManager)
        {
            _context = context;
            _userManager = userManager;
        }

        // GET: Funders
        public async Task<IActionResult> Index()
        {
            var applicationDbContext = _context.Funders.Include(f => f.RequestFunds).Include(x => x.User);
            return View(await applicationDbContext.ToListAsync());
        }

        // GET: Funders/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var funders = await _context.Funders
                .Include(f => f.RequestFunds)
                .Include(x => x.User)
                .FirstOrDefaultAsync(m => m.Id == id);
            if (funders == null)
            {
                return NotFound();
            }

            return View(funders);
        }

        // GET: Funders/Create
        public IActionResult Create()
        {
            ViewData["RequestFundsId"] = new SelectList(_context.RequestFunds, "Id", "Id");
            return View();
        }

        // POST: Funders/Create
        // To protect from overposting attacks, enable the specific properties you want to bind to, for 
        // more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,Amount,RequestFundsId,Receiver,Key")] Funders funders)
        {
            if (ModelState.IsValid)
            {
                var username = await _userManager.FindByNameAsync(User.Identity.Name);
                funders.User = username;
                funders.Key = username.Key;
                FundMethod(username.Key, funders.Receiver, funders.Amount, username.AccountAddress);
                _context.Add(funders);
                ViewBag.Success = "Successfully transfered funds";
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(RequestFundsController.Index));
            }
            ViewData["RequestFundsId"] = new SelectList(_context.RequestFunds, "Id", "Id", funders.RequestFundsId);
            return View(funders);
        }
        public static void FundMethod(string key, string receiver, int amount, string senderAddr)
        {
            string ALGOD_API_ADDR = "https://testnet-algorand.api.purestake.io/ps2"; //find in algod.net
            string ALGOD_API_TOKEN = "B3SU4KcVKi94Jap2VXkK83xx38bsv95K5UZm2lab"; //find in algod.token          
            string SRC_ACCOUNT = key;
            string DEST_ADDR = receiver;
            Account src = new Account(SRC_ACCOUNT);
            AlgodApi algodApiInstance = new AlgodApi(ALGOD_API_ADDR, ALGOD_API_TOKEN);
            try
            {
                var trans = algodApiInstance.TransactionParams();
            }
            catch (ApiException e)
            {
                Console.WriteLine("Exception when calling algod#getSupply:" + e.Message);
            }

            TransactionParametersResponse transParams;
            try
            {
                transParams = algodApiInstance.TransactionParams();
            }
            catch (ApiException e)
            {
                throw new Exception("Could not get params", e);
            }
            var amountsent = Utils.AlgosToMicroalgos(amount);
            var tx = Utils.GetPaymentTransaction(src.Address, new Address(DEST_ADDR), amountsent, "pay message", transParams);
            var signedTx = src.SignTransaction(tx);

            Console.WriteLine("Signed transaction with txid: " + signedTx.transactionID);

            // send the transaction to the network
            try
            {
                var id = Utils.SubmitTransaction(algodApiInstance, signedTx);
                Console.WriteLine("Successfully sent tx with id: " + id.TxId);
                Console.WriteLine(Utils.WaitTransactionToComplete(algodApiInstance, id.TxId));
            }
            catch (ApiException e)
            {
                // This is generally expected, but should give us an informative error message.
                Console.WriteLine("Exception when calling algod#rawTransaction: " + e.Message);
            }
        }
        // GET: Funders/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var funders = await _context.Funders.FindAsync(id);
            if (funders == null)
            {
                return NotFound();
            }
            ViewData["RequestFundsId"] = new SelectList(_context.RequestFunds, "Id", "Id", funders.RequestFundsId);
            return View(funders);
        }

        // POST: Funders/Edit/5
        // To protect from overposting attacks, enable the specific properties you want to bind to, for 
        // more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("Id,Amount,RequestFundsId")] Funders funders)
        {
            if (id != funders.Id)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(funders);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!FundersExists(funders.Id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            ViewData["RequestFundsId"] = new SelectList(_context.RequestFunds, "Id", "Id", funders.RequestFundsId);
            return View(funders);
        }

        // GET: Funders/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var funders = await _context.Funders
                .Include(f => f.RequestFunds)
                .FirstOrDefaultAsync(m => m.Id == id);
            if (funders == null)
            {
                return NotFound();
            }

            return View(funders);
        }

        // POST: Funders/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var funders = await _context.Funders.FindAsync(id);
            _context.Funders.Remove(funders);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

        private bool FundersExists(int id)
        {
            return _context.Funders.Any(e => e.Id == id);
        }
    }
}

Note
Note this code uses account restore from a private key, this is not recommended for production use, and is provided for only for tutorial purposes. To integrate a web application use a wallet SDK to sign and send transactions, such as the AlgoSigner SDK for Chrome Extension. See tutorial here.
Replace SRC_ACCOUNT With your passphrase from creating an account, the quickest way to do this is see example here https://github.com/RileyGe/dotnet-algorand-sdk/blob/master/sdk-examples/CreateAccount.cs
Or create visually with AlgoDEA IDE. https://algodea-docs.bloxbean.com/

Or create from the Algorand Wallet (see App store or Google play) you can change the setting to TestNet, and create a new account and copy off the passphrase.

This account must be funded to perform transactions… this can be done here: https://bank.testnet.algorand.network/

public static void FundMethod(string key, string receiver, int amount, string senderAddr)
        {
            string ALGOD_API_ADDR = "https://testnet-algorand.api.purestake.io/ps2"; //find in algod.net
            string ALGOD_API_TOKEN = "B3SU4KcVKi94Jap2VXkK83xx38bsv95K5UZm2lab"; //find in algod.token          
            string SRC_ACCOUNT = key;
            string DEST_ADDR = receiver;
            Account src = new Account(SRC_ACCOUNT);
            AlgodApi algodApiInstance = new AlgodApi(ALGOD_API_ADDR, ALGOD_API_TOKEN);
            try
            {
                var trans = algodApiInstance.TransactionParams();
            }
            catch (ApiException e)
            {
                Console.WriteLine("Exception when calling algod#getSupply:" + e.Message);
            }

            TransactionParametersResponse transParams;
            try
            {
                transParams = algodApiInstance.TransactionParams();
            }
            catch (ApiException e)
            {
                throw new Exception("Could not get params", e);
            }
            var amountsent = Utils.AlgosToMicroalgos(amount);
            var tx = Utils.GetPaymentTransaction(src.Address, new Address(DEST_ADDR), amountsent, "pay message", transParams);
            var signedTx = src.SignTransaction(tx);

            Console.WriteLine("Signed transaction with txid: " + signedTx.transactionID);

            // send the transaction to the network
            try
            {
                var id = Utils.SubmitTransaction(algodApiInstance, signedTx);
                Console.WriteLine("Successfully sent tx with id: " + id.TxId);
                Console.WriteLine(Utils.WaitTransactionToComplete(algodApiInstance, id.TxId));
            }
            catch (ApiException e)
            {
                // This is generally expected, but should give us an informative error message.
                Console.WriteLine("Exception when calling algod#rawTransaction: " + e.Message);
            }
        }

RequestFunds
Repeat the same procedure for RequestFunds and Update the controller with the one below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AlgorandCrowdfund.Data;
using AlgorandCrowdfund.Models;
using Microsoft.AspNetCore.Identity;

namespace AlgorandCrowdfund.Controllers
{
    public class RequestFundsController : Controller
    {
        private readonly ApplicationDbContext _context;
        private static UserManager<ApplicationUser> _userManager;

        public RequestFundsController(ApplicationDbContext context,
            UserManager<ApplicationUser> userManager)
        {
            _context = context;
            _userManager = userManager;
        }

        // GET: RequestFunds
        public async Task<IActionResult> Index()
        {
            var username = await _userManager.FindByNameAsync(User.Identity.Name);
            ViewBag.UserAddr = username.AccountAddress;
            ViewBag.Key = username.Key;
            return View(await _context.RequestFunds.Include(x => x.User).Include(x => x.Funders).ToListAsync());
        }

        // GET: RequestFunds/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            var mymodel = new ViewModel();
            mymodel.RequestFundsList = RequestFundsList();
            mymodel.FundersList = FundersList();
            mymodel.RequestFunds = await _context.RequestFunds
                .Include(x => x.Funders)
                .Include(x => x.User)
                .FirstOrDefaultAsync(m => m.Id == id);
            mymodel.Funders = await _context.Funders
                .Include(x => x.User)
                .FirstOrDefaultAsync(m => m.Id == id);
            if (mymodel.RequestFunds == null)
            {
                return NotFound();
            }

            return View(mymodel);
        }
        private List<RequestFunds> RequestFundsList()
        {
            List<RequestFunds> list = new List<RequestFunds>();
            list = _context.RequestFunds.Include(x => x.User).ToList();
            return list;
        }
        private List<Funders> FundersList()
        {
            List<Funders> list = new List<Funders>();
            list = _context.Funders.Include(x => x.User).ToList();
            return list;
        }
        public class ViewModel
        {
            public RequestFunds RequestFunds { get; set; }
            public Funders Funders { get; set; }
            public IEnumerable<RequestFunds> RequestFundsList { get; set; }
            public IEnumerable<Funders> FundersList { get; set; }
        }
        // GET: RequestFunds/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: RequestFunds/Create
        // To protect from overposting attacks, enable the specific properties you want to bind to, for 
        // more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,FundTitle,FundDescription,AmountNeeded,AmountRaised,Created")] RequestFunds requestFunds)
        {
            if (ModelState.IsValid)
            {
                var username = await _userManager.FindByNameAsync(User.Identity.Name);
                requestFunds.User = username;
                _context.Add(requestFunds);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(requestFunds);
        }

        // GET: RequestFunds/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var requestFunds = await _context.RequestFunds.FindAsync(id);
            if (requestFunds == null)
            {
                return NotFound();
            }
            return View(requestFunds);
        }

        // POST: RequestFunds/Edit/5
        // To protect from overposting attacks, enable the specific properties you want to bind to, for 
        // more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("Id,FundTitle,FundDescription,AmountNeeded,AmountRaised,Created,ValidTill")] RequestFunds requestFunds)
        {
            if (id != requestFunds.Id)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(requestFunds);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!RequestFundsExists(requestFunds.Id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            return View(requestFunds);
        }

        // GET: RequestFunds/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var requestFunds = await _context.RequestFunds
                .FirstOrDefaultAsync(m => m.Id == id);
            if (requestFunds == null)
            {
                return NotFound();
            }

            return View(requestFunds);
        }

        // POST: RequestFunds/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var requestFunds = await _context.RequestFunds.FindAsync(id);
            _context.RequestFunds.Remove(requestFunds);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

        private bool RequestFundsExists(int id)
        {
            return _context.RequestFunds.Any(e => e.Id == id);
        }
    }
}

7. Setting up the Views

In this step, we will setup the view pages of our application.
EditorImages/2021/03/16 21:34/2021-03-16_22_32_36-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
Fig 7-1

  • Step 1: Funders View Pages
    Create

@model AlgorandCrowdfund.Models.Funders

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Funders</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-controller="Funders" asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Amount" class="control-label"></label>
                <input asp-for="Amount" class="form-control" />
                <span asp-validation-for="Amount" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="RequestFundsId" class="control-label"></label>
                <select asp-for="RequestFundsId" class="form-control" asp-items="ViewBag.RequestFundsId"></select>
            </div>
            <div class="form-group">
                <label asp-for="Receiver" class="control-label"></label>
                <input asp-for="Receiver" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Index

@model IEnumerable<AlgorandCrowdfund.Models.Funders>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Amount)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Key)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Receiver)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.RequestFunds)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Amount)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Key)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Receiver)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.RequestFunds.FundDescription)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Details

@model AlgorandCrowdfund.Models.Funders

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Funders</h4>
    <hr />
    <dl class="row">
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Amount)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Amount)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Key)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Key)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Receiver)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Receiver)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.RequestFunds)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.RequestFunds.FundDescription)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

  • Step 2: RequestFunds View Pages
    Create

@model AlgorandCrowdfund.Models.RequestFunds

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>RequestFunds</h4>
<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">
                <label asp-for="FundTitle" class="control-label"></label>
                <input asp-for="FundTitle" class="form-control" />
                <span asp-validation-for="FundTitle" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FundDescription" class="control-label"></label>
                <input asp-for="FundDescription" class="form-control" />
                <span asp-validation-for="FundDescription" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="AmountNeeded" class="control-label"></label>
                <input asp-for="AmountNeeded" class="form-control" />
                <span asp-validation-for="AmountNeeded" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Created" class="control-label"></label>
                <input asp-for="Created" class="form-control" />
                <span asp-validation-for="Created" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Index

@model IEnumerable<AlgorandCrowdfund.Models.RequestFunds>

@{
    ViewData["Title"] = "Index";
}
@ViewBag.Success

<hr />
Your Address: @ViewBag.UserAddr
Your Key: @ViewBag.Key
<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.FundTitle)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.FundDescription)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.AmountNeeded)
            </th>
            @*<th>
                Backers Count
            </th>*@
            <th>
                @Html.DisplayNameFor(model => model.Created)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.User.UserName)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.FundTitle)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FundDescription)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.AmountNeeded)
                </td>
                @*<td>
                    @item.BackersCount
                </td>*@
                <td>
                    @Html.DisplayFor(modelItem => item.Created)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.User.UserName)
                </td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Details

@model AlgorandCrowdfund.Controllers.RequestFundsController.ViewModel

@{
    ViewData["Title"] = "Details";
}

<div>
    <h4>Campaign</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            Campaign Owner
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.User.UserName, @Model.RequestFunds.User.AccountAddress
        </dd>
        <dt class="col-sm-2">
            Campaign Title
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.FundTitle
        </dd>
        <dt class="col-sm-2">
            Campaign Description
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.FundDescription
        </dd>
        <dt class="col-sm-2">
            Amount Needed
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.AmountNeeded
        </dd>
        @*<dt class="col-sm-2">
            Number of Backers
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.BackersCount
        </dd>*@
        <dt class="col-sm-2">
            Created
        </dt>
        <dd class="col-sm-10">
            @Model.RequestFunds.Created
        </dd>
    </dl>
</div>
<h4>Backers</h4>
@foreach (var a in Model.FundersList)
{
    <a>@a.User.UserName has funded @a.Amount algos to this campaign</a>
    <br />
}

<h4>Fund this Campaign</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-controller="Funders" asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="@Model.Funders.Amount" class="control-label"></label>
                <input asp-for="@Model.Funders.Amount" class="form-control" />
                <span asp-validation-for="@Model.Funders.Amount" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label hidden asp-for="@Model.Funders.Id" class="control-label"></label>
                <input hidden asp-for="@Model.Funders.Id" class="form-control" />
                <span asp-validation-for="@Model.Funders.Id" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label hidden asp-for="@Model.Funders.Receiver" class="control-label"></label>
                <input hidden asp-for="@Model.Funders.Receiver" value="@Model.RequestFunds.User.AccountAddress" class="form-control" />
                <span asp-validation-for="@Model.Funders.Receiver" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="@Model.Funders.RequestFundsId" class="control-label"></label>
                <input asp-for="@Model.Funders.RequestFundsId" value="@Model.RequestFunds.Id" name="@Model.RequestFunds.Id" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>



<div>
    <a asp-action="Edit" asp-route-id="@Model.RequestFunds.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>
<h4>Other Campaign(s)</h4>

<div>
    @foreach (var a in Model.RequestFundsList)
    {
        <a>@a.FundTitle</a>
    }
    <a asp-action="Edit" asp-route-id="@Model.RequestFunds.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

8. Fund Account on Testnet

The following tutorial is for demo purposes and is being worked on the TestNet, no real transactions will be made.
Visit: https://bank.testnet.algorand.network/
Check the “I am not a Robot box”, paste in your account address (user2 address) in the field below as shown in Fig 8-1:
EditorImages/2021/03/16 22:30/2021-02-26_22_39_25-Window.png
Fig 8-1
Visit https://testnet.algoexplorer.io/
As shown in Fig 7-2 below, search for your account details via the Search bar and make sure it is running on Testnet.
EditorImages/2021/03/16 22:43/2021-02-27_00_41_14-Window.png
8-2

9. Launch the Web Application to Test

In this step, we will test our application to see how it works!!

  • Step 1
    Launch the Application by clicking on Debug and start the Application or run the commands Ctrl + f5 to launch.

  • Step 2
    Register an account (admin and user)
    Create a Campaign with the user account as shown in Fig 9-1 below:
    EditorImages/2021/03/16 22:05/2021-03-16_22_56_11-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
    Fig 9-1

Now, Login with the admin account.
Click on the details page of the user campaign to fund it as shown in Fig 9-2
EditorImages/2021/03/16 22:08/2021-03-16_22_59_36-.png
Fig 9-2

Enter the amount you wish to fund/back the campaign with as shown in Fig 9-3 below:
EditorImages/2021/03/16 22:21/2021-03-16_23_16_50-AlgorandCrowdfund_-_Microsoft_Visual_Studio.png
Fig 9-3

10. Source Code

The source code can be found in the GitHub link: https://github.com/KusuConsult-NG/CrowdFundIT

11. Complete Tutorial Video Solution