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
- Algorand .NET SDK Package
- Visual Studio 2019 IDE with .NET Core Framework installed
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
- 2. Install the Algorand Package
- 3. User Account (Registration/Login)
- 4. Create the Models
- 5. Setup the Application Database
- 6. Create the Controllers
- 7. Setting up the Views
- 8. Fund Account on Testnet
- 9. Launch the Web Application to Test
- 10. Source Code
- 11. Complete Tutorial Video Solution
1. Create a .NET Core Web Application
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:
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:
Fig 2-1
3. User Account (Registration/Login)
- Step 1: Create Application User Class
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 ofApplicationUser
in theStartup.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();
}
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:
Fig 3-3
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:
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:
ReplaceIdentityUser
withApplicationUser
as shown in the Image below:
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:
Fig 3-7
4. Create the Models
-
Step 1: Create two additional models,
Funders
andRequestFunds
as shown in Fig 4-1 below
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:
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:
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:
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.
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:
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.
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
anduser
)
Create a Campaign with the user account as shown in Fig 9-1 below:
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
Fig 9-2
Enter the amount you wish to fund/back the campaign with as shown in Fig 9-3 below:
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