Leasing .NET Core - Xamarin Forms + Prism - Xamarin Classic + MVVM Cross
Leasing .NET Core - Xamarin Forms + Prism - Xamarin Classic + MVVM Cross
NET Core
Xamarin Forms + Prism
Xamarin Classic + MVVM Cross
Leasing Example
By Zulu
Medellín 2019
MyLeasing - Sample By Zulu
Index
Leasing example: functionality matrix 4
Login X X X X X
Register X X X X
Modify profile X X X X X
Recover password X X X X X
Admin managers X
Admin owners X
Admin leeses X
Admin contracts X
See my contracts X X X X
Admin properties X X X
Frontend
(Mobile)
g.Prism.A g.Prism.i sing.
ndroid OS sing.
Cross.
Cross.
MyLeasing.Prism Androi
iOS
d
Backend
(Web)
Front End
API
Web (Admin)
MyLeasing.Common (.NET
Standard)
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class Owner
{
public int Id { get; set; }
[Display(Name = "Document")]
[MaxLength(20, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
public string Address { get; set; }
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Data
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<Owner> Owners { get; set; }
}
}
3. Add the connection string to the configuration json file: appsettings.json (see the SQL Server Object Explorer):
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection":
"Server=(localdb)\\MSSQLLocalDB;Database=MyLeasing;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Note: You must be sure of the servers names in your installation, you can check it out by clicking in SQL Server Object
Explorer:
In this case, there are three available servers: (localdb)\MSSQLLocalDB, (localdb)\ProjectsV13 and (localdb)\v11.0. Or you
can explore your server by clicking on “Add SQL Server” icon:
services.AddDbContext<DataContext>(cfg =>
{
cfg.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
5. Save changes and run those commands by command line in the same folder that is the web project:
PM> update-database
PM> add-migration InitialDb
PM> update-database
Modify DB
We will complete the DB initially with these entities and relationships:
1 1
Owner Property Type
1
*
* Contract * 1
Property *
* 1
1
*
Lessee Property Image
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class PropertyType
{
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class Lessee
{
public int Id { get; set; }
[Display(Name = "Document")]
[MaxLength(20, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string Document { get; set; }
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
public string Address { get; set; }
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class Property
{
public int Id { get; set; }
[Display(Name = "Neighborhood")]
[MaxLength(50, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[Display(Name = "Address")]
[MaxLength(50, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string Address { get; set; }
[Display(Name = "Price")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[DisplayFormat(DataFormatString = "{0:C2}", ApplyFormatInEditMode = false)]
public decimal Price { get; set; }
[Display(Name = "Stratum")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public int Stratum { get; set; }
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class PropertyImage
{
public int Id { get; set; }
[Display(Name = "Image")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string ImageUrl { get; set; }
using System;
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class Contract
{
public int Id { get; set; }
[Display(Name = "Price")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[DisplayFormat(DataFormatString = "{0:C2}", ApplyFormatInEditMode = false)]
public decimal Price { get; set; }
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Data
{
using MyLeasing.Web.Data.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MyLeasing.Web.Data
{
public class SeedDb
{
private readonly DataContext _context;
await _context.SaveChangesAsync();
}
}
private async Task CheckLesseesAsync()
{
if (!_context.Lessees.Any())
{
AddLessee("876543", "Ramon", "Gamboa", "234 3232", "310 322 3221", "Calle Luna Calle Sol");
AddLessee("654565", "Julian", "Martinez", "343 3226", "300 322 3221", "Calle 77 #22 21");
AddLessee("214231", "Carmenza", "Ruis", "450 4332", "350 322 3221", "Carrera 56 #22 21");
await _context.SaveChangesAsync();
}
}
private void AddLessee(string document, string firstName, string lastName, string fixedPhone, string cellPhone, string
address)
{
_context.Lessees.Add(new Lessee
{
Address = address,
CellPhone = cellPhone,
Document = document,
FirstName = firstName,
FixedPhone = fixedPhone,
LastName = lastName
});
}
if (!_context.Properties.Any())
{
AddProperty("Calle 43 #23 32", "Poblado", owner, propertyType, 800000M, 2, 72, 4);
AddProperty("Calle 12 Sur #2 34", "Envigado", owner, propertyType, 950000M, 3, 81, 3);
await _context.SaveChangesAsync();
}
}
FirstName = firstName,
FixedPhone = fixedPhone,
LastName = lastName
});
}
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using MyLeasing.Web.Data;
namespace MyLeasing.Web
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
RunSeeding(host);
host.Run();
}
}
}
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<DataContext>(cfg =>
{
cfg.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddTransient<SeedDb>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
4. Test it.
1
UserRole * 1
Role Property Type
*
1
1 1 1 1
User Owner
1 1
1
* *
1
Manager Contract * Property *
* 1
*
1 1
Lessee Property Image
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class User : IdentityUser
{
[Display(Name = "Document")]
[MaxLength(20, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string Document { get; set; }
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
public string Address { get; set; }
using System.Collections.Generic;
namespace MyLeasing.Web.Data.Entities
{
public class Owner
{
public int Id { get; set; }
using System.Collections.Generic;
namespace MyLeasing.Web.Data.Entities
{
public class Lessee
{
public int Id { get; set; }
namespace MyLeasing.Web.Data.Entities
{
public class Manager
{
public int Id { get; set; }
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Data
{
public class DataContext : IdentityDbContext<User>
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
6. Create the folder Helpers and inside it add the interface IUserHelper:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Helpers
{
public interface IUserHelper
{
Task<User> GetUserByEmailAsync(string email);
using Microsoft.AspNetCore.Identity;
using MyLeasing.Web.Data.Entities;
using System.Threading.Tasks;
namespace MyLeasing.Web.Helpers
{
public class UserHelper : IUserHelper
{
private readonly UserManager<User> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public UserHelper(
UserManager<User> userManager,
RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
{
await _roleManager.CreateAsync(new IdentityRole
{
Name = roleName
});
}
}
services.AddScoped<IUserHelper, UserHelper>();
using System;
using System.Linq;
using System.Threading.Tasks;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Helpers;
namespace MyLeasing.Web.Data
{
public class SeedDb
{
private readonly DataContext _context;
private readonly IUserHelper _userHelper;
public SeedDb(
DataContext context,
IUserHelper userHelper)
{
_context = context;
_userHelper = userHelper;
}
await _context.SaveChangesAsync();
}
}
if (!_context.Managers.Any())
{
_context.Managers.Add(new Manager { User = user });
await _context.SaveChangesAsync();
}
}
private async Task<User> CheckUserAsync(string document, string firstName, string lastName, string email, string phone,
string address, string role)
{
var user = await _userHelper.GetUserByEmailAsync(email);
if (user == null)
{
user = new User
{
FirstName = firstName,
LastName = lastName,
Email = email,
UserName = email,
PhoneNumber = phone,
Address = address,
Document = document
};
return user;
}
private void AddProperty(string address, string neighborhood, Owner owner, PropertyType propertyType, decimal price, int
rooms, int squareMeters, int stratum)
{
_context.Properties.Add(new Property
{
Address = address,
HasParkingLot = true,
IsAvailable = true,
Neighborhood = neighborhood,
Owner = owner,
Price = price,
PropertyType = propertyType,
Rooms = rooms,
SquareMeters = squareMeters,
Stratum = stratum
});
}
}
}
11. Drop the database and add the new migrations with those commands:
PM> drop-database
PM> add-migration Users
PM> update-database
services.AddDbContext<DataContext>(cfg =>
{
cfg.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddTransient<SeedDb>();
services.AddScoped<IUserHelper, UserHelper>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Username { get; set; }
[Required]
[MinLength(6)]
public string Password { get; set; }
Task LogoutAsync();
public UserHelper(
UserManager<User> userManager,
RoleManager<IdentityRole> roleManager,
SignInManager<User> signInManager)
{
_userManager = userManager;
_roleManager = roleManager;
_signInManager = signInManager;
}
using Microsoft.AspNetCore.Mvc;
using MyLeasing.Web.Helpers;
using MyLeasing.Web.Models;
using System.Linq;
using System.Threading.Tasks;
namespace MyLeasing.Web.Controllers
{
public class AccountController : Controller
{
private readonly IUserHelper _userHelper;
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
@model MyLeasing.Web.Models.LoginViewModel
@{
ViewData["Title"] = "Login";
}
<h2>Login</h2>
<div class="row">
<div class="col-md-4 offset-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<div class="form-group">
<label asp-for="Username">Username</label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-warning"></span>
</div>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<div class="form-group">
<label asp-for="Password">Password</label>
<input asp-for="Password" type="password" class="form-control" />
<span asp-validation-for="Password" class="text-warning"></span>
</div>
<div class="form-group">
<div class="form-check">
<input asp-for="RememberMe" type="checkbox" class="form-check-input" />
<label asp-for="RememberMe" class="form-check-label">Remember Me?</label>
</div>
<span asp-validation-for="RememberMe" class="text-warning"></span>
</div>
<div class="form-group">
<input type="submit" value="Login" class="btn btn-success" />
<a asp-action="Register" class="btn btn-primary">Register New User</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
[Authorize(Roles = "Manager")]
@if (User.Identity.IsAuthenticated)
{
<li><a asp-area="" asp-controller="Account" asp-action="ChangeUser">@User.Identity.Name</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Logout">Logout</a></li>
}
else
{
<li><a asp-area="" asp-controller="Account" asp-action="Login">Login</a></li>
}
</ul>
</div>
8. Test it.
@model IEnumerable<MyLeasing.Web.Data.Entities.Owner>
@{
ViewData["Title"] = "Index";
<h2>Owners</h2>
<p>
<a asp-action="Create" class="btn btn-primary">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Document)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Email)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.PhoneNumber)
</th>
<th>
Properties
</th>
<th>
Contracts
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.User.Document)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.PhoneNumber)
</td>
<td>
@Html.DisplayFor(modelItem => item.Properties.Count)
</td>
<td>
3. Test it.
{
return NotFound();
}
return View(owner);
}
@model MyLeasing.Web.Data.Entities.Owner
@{
ViewData["Title"] = "Details";
}
<h2>Owner</h2>
<div>
<h4>Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Document)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.FirstName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.FirstName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.PhoneNumber)
</dt>
<dd>
@Html.DisplayFor(model => model.User.PhoneNumber)
</dd>
<dt>
Properties
</dt>
<dd>
@Html.DisplayFor(model => model.Properties.Count)
</dd>
<dt>
Contracts
</dt>
<dd>
@Html.DisplayFor(model => model.Contracts.Count)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning">Edit</a>
<a asp-action="AddProperty" asp-route-id="@Model.Id" class="btn btn-primary">Add Property</a>
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
<h4>Properties</h4>
@if (Model.Properties.Count == 0)
{
<h5>Not properties added yet.</h5>
}
else
{
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().PropertyType.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().SquareMeters)
</th>
<th>
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.SquareMeters)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rooms)
</td>
<td>
@Html.DisplayFor(modelItem => item.Stratum)
</td>
<td>
@Html.DisplayFor(modelItem => item.HasParkingLot)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsAvailable)
</td>
<td>
@Html.DisplayFor(modelItem => item.PropertyImages.Count)
</td>
<td>
@Html.DisplayFor(modelItem => item.Contracts.Count)
</td>
<td>
<a asp-action="EditProperty" asp-route-id="@item.Id" class="btn btn-warning">Edit</a>
<a asp-action="DetailsProperty" asp-route-id="@item.Id" class="btn btn-info">Details</a>
<a asp-action="DeleteProperty" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
6. Test it.
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class AddUserViewModel
{
[Display(Name = "Email")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[EmailAddress]
public string Username { get; set; }
[Display(Name = "Document")]
[MaxLength(20, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string Document { get; set; }
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
public string Address { get; set; }
[Display(Name = "Password")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6, ErrorMessage = "The {0} field must contain between {2} and {1} characters.")]
public string Password { get; set; }
@model MyLeasing.Web.Models.AddUserViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Owner</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="Username" class="control-label"></label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Document" class="control-label"></label>
<input asp-for="Document" class="form-control" />
<span asp-validation-for="Document" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PhoneNumber" class="control-label"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PasswordConfirm" class="control-label"></label>
<input asp-for="PasswordConfirm" class="form-control" />
<span asp-validation-for="PasswordConfirm" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await AddUser(view);
if (user == null)
{
ModelState.AddModelError(string.Empty, "This email is already used.");
return View(view);
}
_dataContext.Owners.Add(owner);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(view);
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Models
{
public class PropertyViewModel : Property
{
public int OwnerId { get; set; }
return NotFound();
}
return View(view);
}
[HttpPost]
public async Task<IActionResult> AddProperty(PropertyViewModel view)
{
if (ModelState.IsValid)
{
var property = await ToPropertyAsync(view);
_dataContext.Properties.Add(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(Details)}/{view.OwnerId}");
}
return View(view);
}
IsAvailable = view.IsAvailable,
Neighborhood = view.Neighborhood,
Price = view.Price,
Rooms = view.Rooms,
SquareMeters = view.SquareMeters,
Stratum = view.Stratum,
Owner = await _dataContext.Owners.FindAsync(view.OwnerId),
PropertyType = await _dataContext.PropertyTypes.FindAsync(view.PropertyTypeId),
Remarks = view.Remarks
};
}
return list;
}
@model MyLeasing.Web.Models.PropertyViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="AddProperty">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="OwnerId" />
<div class="form-group">
<label asp-for="PropertyTypeId" class="control-label"></label>
<select asp-for="PropertyTypeId" asp-items="Model.PropertyTypes" class="form-control"></select>
<span asp-validation-for="PropertyTypeId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Neighborhood" class="control-label"></label>
<input asp-for="Neighborhood" class="form-control" />
<span asp-validation-for="Neighborhood" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SquareMeters" class="control-label"></label>
<input asp-for="SquareMeters" class="form-control" />
<span asp-validation-for="SquareMeters" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Rooms" class="control-label"></label>
<input asp-for="Rooms" class="form-control" />
<span asp-validation-for="Rooms" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Stratum" class="control-label"></label>
<input asp-for="Stratum" class="form-control" />
<span asp-validation-for="Stratum" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="HasParkingLot" /> @Html.DisplayNameFor(model => model.HasParkingLot)
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsAvailable" /> @Html.DisplayNameFor(model => model.IsAvailable)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="Remarks" class="control-label"></label>
<textarea asp-for="Remarks" class="form-control"></textarea>
<span asp-validation-for="Remarks" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Details" asp-route-id="@Model.OwnerId" class="btn btn-success">Back to Owner</a>
</div>
</form>
</div>
</div>
Remarks = property.Remarks,
};
}
[HttpPost]
public async Task<IActionResult> EditProperty(PropertyViewModel view)
{
if (ModelState.IsValid)
{
var property = await ToPropertyAsync(view);
_dataContext.Properties.Update(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(Details)}/{view.OwnerId}");
}
return View(view);
}
@model MyLeasing.Web.Models.PropertyViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="EditProperty">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="OwnerId" />
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="PropertyTypeId" class="control-label"></label>
<select asp-for="PropertyTypeId" asp-items="Model.PropertyTypes" class="form-control"></select>
<span asp-validation-for="PropertyTypeId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Neighborhood" class="control-label"></label>
<input asp-for="Neighborhood" class="form-control" />
<span asp-validation-for="Neighborhood" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SquareMeters" class="control-label"></label>
<input asp-for="SquareMeters" class="form-control" />
<div class="form-group">
<label asp-for="Rooms" class="control-label"></label>
<input asp-for="Rooms" class="form-control" />
<span asp-validation-for="Rooms" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Stratum" class="control-label"></label>
<input asp-for="Stratum" class="form-control" />
<span asp-validation-for="Stratum" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="HasParkingLot" /> @Html.DisplayNameFor(model => model.HasParkingLot)
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsAvailable" /> @Html.DisplayNameFor(model => model.IsAvailable)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="Remarks" class="control-label"></label>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-action="Details" asp-route-id="@Model.OwnerId" class="btn btn-success">Back to Owner</a>
</div>
</form>
</div>
</div>
if (property == null)
{
return NotFound();
}
return View(property);
}
@model MyLeasing.Web.Data.Entities.Property
@{
ViewData["Title"] = "Details";
}
<h2>Property</h2>
<div class="row">
<div class="col-md-6">
<div>
<h4>Owner</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Owner.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.Document)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.FirstName)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.FirstName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.PhoneNumber)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.PhoneNumber)
</dd>
</dl>
</div>
</div>
<div class="col-md-6">
<div>
<h4>Property Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SquareMeters)
</dt>
<dd>
@Html.DisplayFor(model => model.SquareMeters)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Rooms)
</dt>
<dd>
@Html.DisplayFor(model => model.Rooms)
</dd>
<dt>
@Html.DisplayNameFor(model => model.HasParkingLot)
</dt>
<dd>
@Html.DisplayFor(model => model.HasParkingLot)
</dd>
<dt>
@Html.DisplayNameFor(model => model.IsAvailable)
</dt>
<dd>
@Html.DisplayFor(model => model.IsAvailable)
</dd>
<dt>
Contracts
</dt>
<dd>
@Html.DisplayFor(model => model.Contracts.Count)
</dd>
</dl>
</div>
</div>
</div>
<div>
<a asp-action="EditProperty" asp-route-id="@Model.Id" class="btn btn-warning">Edit</a>
<a asp-action="AddImage" asp-route-id="@Model.Id" class="btn btn-primary">Add Image</a>
<a asp-action="AddContract" asp-route-id="@Model.Id" class="btn btn-default">Add Contract</a>
<a asp-action="Details" asp-route-id="@Model.Owner.Id" class="btn btn-success">Back to Owner</a>
</div>
<div class="row">
<div class="col-md-3">
<div>
<h4>Images</h4>
@if (Model.PropertyImages.Count == 0)
{
<h5>Not images added yet.</h5>
}
else
{
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.PropertyImages.FirstOrDefault().ImageUrl)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.PropertyImages)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(item.ImageUrl))
{
<img src="@Url.Content(item.ImageUrl)" alt="Image" style="width:200px;height:200px;max-width:
100%; height: auto;" />
}
</td>
<td>
<a asp-action="DeleteImage" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
<div class="col-md-9">
<div>
<h4>Contracts</h4>
@if (Model.Contracts.Count == 0)
{
<h5>Not contracts added yet.</h5>
}
else
{
<table class="table">
<thead>
<tr>
<th>
Lessee
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Remarks)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().EndDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().IsActive)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contracts)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Lessee.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Remarks)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDateLocal)
</td>
<td>
@Html.DisplayFor(modelItem => item.EndDateLocal)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsActive)
</td>
<td>
<a asp-action="EditContract" asp-route-id="@item.Id" class="btn btn-warning">Edit</a>
<a asp-action="DetailsContract" asp-route-id="@item.Id" class="btn btn-info">Details</a>
<a asp-action="DeleteContract" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
</div>
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Data.Entities
{
public class PropertyImage
{
public int Id { get; set; }
[Display(Name = "Image")]
public string ImageUrl { get; set; }
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Models
{
public class PropertyImageViewModel : PropertyImage
{
[Display(Name = "Image")]
public IFormFile ImageFile { get; set; }
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace MyLeasing.Web.Helpers
{
public interface IImageHelper
{
Task<string> UploadImageAsync(IFormFile imageFile);
}
}
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace MyLeasing.Web.Helpers
{
public class ImageHelper : IImageHelper
{
public async Task<string> UploadImageAsync(IFormFile imageFile)
{
var guid = Guid.NewGuid().ToString();
var file = $"{guid}.jpg";
var path = Path.Combine(
Directory.GetCurrentDirectory(),
"wwwroot\\images\\Properties",
file);
return $"~/images/Properties/{file}";
}
}
}
public OwnersController(
DataContext dataContext,
IUserHelper userHelper,
ICombosHelper combosHelper,
IConverterHelper converterHelper,
IImageHelper imageHelper)
{
_dataContext = dataContext;
_userHelper = userHelper;
_combosHelper = combosHelper;
_converterHelper = converterHelper;
_imageHelper = imageHelper;
}
...
public async Task<IActionResult> AddImage(int? id)
{
if (id == null)
{
return NotFound();
}
return View(model);
}
[HttpPost]
public async Task<IActionResult> AddImage(PropertyImageViewModel model)
{
if (ModelState.IsValid)
{
var path = string.Empty;
if (model.ImageFile != null)
{
path = await _imageHelper.UploadImageAsync(model.ImageFile);
}
_dataContext.PropertyImages.Add(propertyImage);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsProperty)}/{model.Id}");
}
return View(model);
}
@model MyLeasing.Web.Models.PropertyImageViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Add Image</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="AddImage" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="ImageFile" class="control-label"></label>
<input asp-for="ImageFile" class="form-control" type="file" />
<span asp-validation-for="ImageFile" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="DetailsProperty" asp-route-id="@Model.Id" class="btn btn-success">Back to Property</a>
</div>
</form>
</div>
</div>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Models
{
public class ContractViewModel : Contract
{
public int OwnerId { get; set; }
return View(view);
}
return list;
}
[HttpPost]
public async Task<IActionResult> AddContract(ContractViewModel view)
{
if (ModelState.IsValid)
{
var contract = await ToContractAsync(view);
_dataContext.Contracts.Add(contract);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsProperty)}/{view.OwnerId}");
}
return View(view);
}
@model MyLeasing.Web.Models.ContractViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Contract</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="AddContract">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="OwnerId" />
<input type="hidden" asp-for="PropertyId" />
<div class="form-group">
<label asp-for="LesseeId" class="control-label"></label>
<select asp-for="LesseeId" asp-items="Model.Lessees" class="form-control"></select>
<span asp-validation-for="LesseeId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Remarks" class="control-label"></label>
<textarea asp-for="Remarks" class="form-control"></textarea>
<span asp-validation-for="Remarks" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndDate" class="control-label"></label>
<input asp-for="EndDate" class="form-control" />
<span asp-validation-for="EndDate" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsActive" /> @Html.DisplayNameFor(model => model.IsActive)
</label>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="DetailsProperty" asp-route-id="@Model.PropertyId" class="btn btn-success">Back to Property</a>
</div>
</form>
</div>
</div>
IEnumerable<SelectListItem> GetComboLessees();
return list;
}
return View(_converterHelper.ToContractViewModel(contract));
}
[HttpPost]
public async Task<IActionResult> EditContract(ContractViewModel view)
{
if (ModelState.IsValid)
{
var contract = await _converterHelper.ToContractAsync(view, false);
_dataContext.Contracts.Update(contract);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsProperty)}/{view.OwnerId}");
return View(view);
}
@model MyLeasing.Web.Models.ContractViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Contract</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="EditContract">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<input type="hidden" asp-for="OwnerId" />
<input type="hidden" asp-for="PropertyId" />
<div class="form-group">
<label asp-for="LesseeId" class="control-label"></label>
<select asp-for="LesseeId" asp-items="Model.Lessees" class="form-control"></select>
<span asp-validation-for="LesseeId" class="text-danger"></span>
</div>
<div class="form-group">
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndDate" class="control-label"></label>
<input asp-for="EndDate" class="form-control" />
<span asp-validation-for="EndDate" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsActive" /> @Html.DisplayNameFor(model => model.IsActive)
</label>
</div>
</div>
<div class="form-group">
…
<td>
<button data-id="@item.Id" class="btn btn-danger deleteImage" data-toggle="modal" data-
target="#deleteDialog">Delete</button>
</td>
…
<td>
<a asp-action="EditContract" asp-route-id="@item.Id" class="btn btn-warning">Edit</a>
<a asp-action="DetailsContract" asp-route-id="@item.Id" class="btn btn-info">Details</a>
<button data-id="@item.Id" class="btn btn-danger deleteContract" data-toggle="modal" data-
target="#deleteDialog">Delete</button>
</td>
…
</div>
<!--Delete Item-->
<div class="modal fade" id="deleteDialog" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Item</h5>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$(document).ready(function () {
// Delete item
var item_to_delete;
var action_to_delete;
$('.deleteImage').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
action_to_delete = 1;
});
$('.deleteContract').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
action_to_delete = 2;
});
$("#btnYesDelete").click(function () {
if (action_to_delete == 1) {
window.location.href = '/Owners/DeleteImage/' + item_to_delete;
} else {
window.location.href = '/Owners/DeleteContract/' + item_to_delete;
}
});
});
</script>
}
_dataContext.PropertyImages.Remove(propertyImage);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsProperty)}/{propertyImage.Property.Id}");
}
_dataContext.Contracts.Remove(contract);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsProperty)}/{contract.Property.Id}");
}
{
var user = await GetUserByEmailAsync(email);
if (user == null)
{
return true;
}
<td>
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-warning">Edit</a>
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info">Details</a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog">Delete</button>
</td>
</tr>
}
</tbody>
</table>
<!--Delete Item-->
<div class="modal fade" id="deleteDialog" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Item</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Do you want to delete the record?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="btnYesDelete">Delete</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$(document).ready(function () {
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Owners/Delete/' + item_to_delete;
});
});
</script>
}
46. Modify the Delete method in OwnersController and delete the HttpPost:
_dataContext.Owners.Remove(owner);
await _dataContext.SaveChangesAsync();
await _userHelper.DeleteUserAsync(owner.User.Email);
return RedirectToAction(nameof(Index));
}
{
return await _userManager.UpdateAsync(user);
}
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class EditUserViewModel
{
public int Id { get; set; }
[Display(Name = "Document")]
[MaxLength(20, ErrorMessage = "The {0} field can not have more than {1} characters.")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
public string Document { get; set; }
[MaxLength(100, ErrorMessage = "The {0} field can not have more than {1} characters.")]
public string Address { get; set; }
return View(view);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditUserViewModel view)
{
if (ModelState.IsValid)
{
var owner = await _dataContext.Owners
.Include(o => o.User)
.FirstOrDefaultAsync(o => o.Id == view.Id);
owner.User.Document = view.Document;
owner.User.FirstName = view.FirstName;
owner.User.LastName = view.LastName;
owner.User.Address = view.Address;
owner.User.PhoneNumber = view.PhoneNumber;
await _userHelper.UpdateUserAsync(owner.User);
return RedirectToAction(nameof(Index));
}
return View(view);
}
@model MyLeasing.Web.Models.EditUserViewModel
@{
ViewData["Title"] = "Edit";
<h2>Edit</h2>
<h4>Owner</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Document" class="control-label"></label>
<input asp-for="Document" class="form-control" />
<span asp-validation-for="Document" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<div class="form-group">
<label asp-for="PhoneNumber" class="control-label"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
_dataContext.Properties.Remove(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(Details)}/{property.Owner.Id}");
}
…
<td>
<a asp-action="EditProperty" asp-route-id="@item.Id" class="btn btn-warning">Edit</a>
<a asp-action="DetailsProperty" asp-route-id="@item.Id" class="btn btn-info">Details</a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog">Delete</button>
</td>
…
<!--Delete Item-->
<div class="modal fade" id="deleteDialog" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Item</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Do you want to delete the record?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="btnYesDelete">Delete</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$(document).ready(function () {
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Owners/DeleteProperty/' + item_to_delete;
});
});
</script>
}
return View(contract);
}
@model MyLeasing.Web.Data.Entities.Contract
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Contract</h4>
<hr />
<dl class="dl-horizontal">
<dt>
Owner
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.FullNameWithDocument)
</dd>
<dt>
Lessee
</dt>
<dd>
@Html.DisplayFor(model => model.Lessee.User.FullNameWithDocument)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Property.PropertyType.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Property.PropertyType.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Property.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Property.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Property.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Property.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Remarks)
</dt>
<dd>
@Html.DisplayFor(model => model.Remarks)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.EndDate)
</dt>
<dd>
@Html.DisplayFor(model => model.EndDate)
</dd>
<dt>
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
namespace MyLeasing.Web.Controllers
{
[Authorize(Roles = "Manager")]
public class ContractsController : Controller
{
private readonly DataContext _dataContext;
return View(contract);
}
}
}
@model IEnumerable<MyLeasing.Web.Data.Entities.Contract>
@{
ViewData["Title"] = "Index";
}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Contracts</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
Owner
</th>
<th>
Lessee
</th>
<th>
Property Type
</th>
<th>
@Html.DisplayNameFor(model => model.Property.Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Property.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.EndDate)
</th>
<th>
@Html.DisplayNameFor(model => model.IsActive)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Owner.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Lessee.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.PropertyType.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.Neighborhood)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.EndDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsActive)
</td>
<td>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
});
</script>
}
@model MyLeasing.Web.Data.Entities.Contract
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Contract</h4>
<hr />
<dl class="dl-horizontal">
<dt>
Owner
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.FullNameWithDocument)
</dd>
<dt>
Lessee
</dt>
<dd>
@Html.DisplayFor(model => model.Lessee.User.FullNameWithDocument)
</dd>
<dt>
Property Type
</dt>
<dd>
@Html.DisplayFor(model => model.Property.PropertyType.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Property.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Property.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Property.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Property.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
63. Delete the methods and views Create, Edit and Delete for ContractsController.
Add API
1. Add the EmailRequest class:
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Common.Models
{
public class EmailRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}
namespace MyLeasing.Common.Models
{
public class LesseeResponse
{
public int Id { get; set; }
using System;
namespace MyLeasing.Common.Models
{
public class ContractResponse
{
public int Id { get; set; }
namespace MyLeasing.Common.Models
{
public class PropertyImageResponse
{
public int Id { get; set; }
}
}
using System.Collections.Generic;
namespace MyLeasing.Common.Models
{
public class PropertyResponse
{
public int Id { get; set; }
using System.Collections.Generic;
namespace MyLeasing.Common.Models
{
public class OwnerResponse
{
public int Id { get; set; }
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Common.Models;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
public class OwnersController : ControllerBase
{
private readonly DataContext _dataContext;
[HttpPost]
[Route("GetOwnerByEmail")]
public async Task<IActionResult> GetOwnerByEmailAsync(EmailRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
if (owner == null)
{
return NotFound();
}
}).ToList(),
PropertyType = p.PropertyType.Name,
Remarks = p.Remarks,
Rooms = p.Rooms,
SquareMeters = p.SquareMeters,
Stratum = p.Stratum
}).ToList()
};
return Ok(response);
}
9. Test it on postmant.
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection":
"Server=(localdb)\\MSSQLLocalDB;Database=MyLeasing;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Tokens": {
"Key": "asdfghjikbnvcgfdsrtfyhgcvgfxdgc",
"Issuer": "localhost",
"Audience": "users"
}
}
public AccountController(
IUserHelper userHelper,
IConfiguration configuration)
{
_userHelper = userHelper;
_configuration = configuration;
}
[HttpPost]
public async Task<IActionResult> CreateToken([FromBody] LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userHelper.GetUserByEmailAsync(model.Username);
if (user != null)
{
var result = await _userHelper.ValidatePasswordAsync(
user,
model.Password);
if (result.Succeeded)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
return BadRequest();
}
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
6. Add the new configuration for validate the tokens in Startup class:
services.AddDbContext<DataContext>(cfg =>
{
cfg.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
services.AddTransient<SeedDb>();
services.AddScoped<IUserHelper, UserHelper>();
7. Test it.
Publish on Azure
And re-publish.
Login
1. Delete the MainPage and MainPageViewModel.
<StackLayout
Padding="10">
<Label
Text="Email"/>
<Entry
Keyboard="Email"
Placeholder="Enter your email..."
Text="{Binding Email}"/>
<Label
Text="Password"/>
<Entry
IsPassword="True"
Placeholder="Enter your password..."
Text="{Binding Password}"/>
<ActivityIndicator
IsRunning="{Binding IsRunning}"
VerticalOptions="CenterAndExpand"/>
<Button
Command="{Binding LoginCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Login"/>
</StackLayout>
</ContentPage>
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class LoginPageViewModel : ViewModelBase
{
private string _password;
private bool _isRunning;
private bool _isEnabled;
private DelegateCommand _loginCommand;
{
Title = "Login";
IsEnabled = true;
}
return;
}
if (string.IsNullOrEmpty(Password))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a password.", "Accept");
return;
}
4. Test it.
namespace MyLeasing.Common.Models
{
public class Response
{
public bool IsSuccess { get; set; }
namespace MyLeasing.Common.Models
{
public class TokenRequest
{
public string Username { get; set; }
using System;
namespace MyLeasing.Common.Models
{
public class TokenResponse
{
public string Token { get; set; }
using System.Threading.Tasks;
using MyLeasing.Common.Models;
namespace MyLeasing.Common.Services
{
public interface IApiService
{
Task<Response> GetOwnerByEmail(
string urlBase,
string servicePrefix,
string controller,
string tokenType,
string accessToken,
string email);
Task<Response> GetTokenAsync(
string urlBase,
string servicePrefix,
string controller,
TokenRequest request);
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using MyLeasing.Common.Models;
using Newtonsoft.Json;
namespace MyLeasing.Common.Services
{
if (!response.IsSuccessStatusCode)
{
return new Response
{
IsSuccess = false,
Message = result,
};
}
{
IsSuccess = true,
Result = token
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message
};
}
}
if (!response.IsSuccessStatusCode)
{
return new Response
{
IsSuccess = false,
Message = result,
};
}
</ResourceDictionary>
</Application.Resources>
</prism:PrismApplication>
await NavigationService.NavigateAsync("NavigationPage/LoginPage");
}
containerRegistry.RegisterForNavigation<Login, LoginViewModel>();
}
if (string.IsNullOrEmpty(Password))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a password.", "Accept");
return;
}
IsRunning = true;
IsEnabled = false;
if (!response.IsSuccess)
{
IsEnabled = true;
IsRunning = false;
await App.Current.MainPage.DisplayAlert("Error", "User or password incorrect.", "Accept");
Password = string.Empty;
return;
}
IsEnabled = true;
IsRunning = false;
namespace MyLeasing.Common.Models
{
public class Response<T> where T : class
{
public bool IsSuccess { get; set; }
Task<Response<OwnerResponse>> GetOwnerByEmailAsync(
string urlBase,
string servicePrefix,
string controller,
string tokenType,
string accessToken,
string email);
Task<Response<TokenResponse>> GetTokenAsync(
string urlBase,
string servicePrefix,
string controller,
TokenRequest request);
string controller,
TokenRequest request)
{
try
{
var requestString = JsonConvert.SerializeObject(request);
var content = new StringContent(requestString, Encoding.UTF8, "application/json");
var client = new HttpClient
{
BaseAddress = new Uri(urlBase)
};
if (!response.IsSuccessStatusCode)
{
return new Response<TokenResponse>
{
IsSuccess = false,
Message = result,
};
}
if (!response.IsSuccessStatusCode)
{
return new Response<OwnerResponse>
{
IsSuccess = false,
Message = result,
};
}
...
5. Test it.
IsRunning = true;
IsEnabled = false;
{
IsEnabled = true;
IsRunning = false;
await App.Current.MainPage.DisplayAlert("Error", "Check the internet connection.", "Accept");
return;
}
<StackLayout
Padding="10">
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding Title}"
VerticalOptions="CenterAndExpand"/>
</StackLayout>
</ContentPage>
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class PropertiesViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
IsEnabled = true;
IsRunning = false;
await _navigationService.NavigateAsync("Properties");
}
3. To avoid entering email and password everytime, temporarily add those lines to LoginViewModel constructor:
public LoginViewModel(
INavigationService navigationService,
IApiService apiService) : base(navigationService)
{
_navigationService = navigationService;
_apiService = apiService;
Title = "Login";
IsEnabled = true;
4. Test it.
IsEnabled = true;
IsRunning = false;
if (parameters.ContainsKey("token"))
{
_token = parameters.GetValue<TokenResponse>("token");
}
if (parameters.ContainsKey("owner"))
{
_owner = parameters.GetValue<OwnerResponse>("owner");
Title = _owner.FullName;
}
}
8. Test it.
using MyLeasing.Common.Models;
namespace MyLeasing.Prism.ViewModels
{
public class PropertyItemViewModel : PropertyResponse
{
}
}
<StackLayout
Padding="10">
<ListView
HasUnevenRows="True"
SeparatorVisibility="None"
IsRefreshing="{Binding IsRefreshing}"
ItemsSource="{Binding Properties}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame
CornerRadius="20"
HasShadow="True"
Margin="0,0,0,5">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectPropertyCommand}"/>
</Frame.GestureRecognizers>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding FirstImage}"
WidthRequest="100">
</Image>
<StackLayout
Grid.Column="1"
VerticalOptions="Center">
<Label
FontAttributes="Bold"
FontSize="Medium"
Text="{Binding Neighborhood}"
TextColor="Black">
</Label>
<Label
Text="{Binding Address}"
TextColor="Black">
</Label>
<Label
Text="{Binding Price, StringFormat='{0:C2}'}"
TextColor="Navy">
</Label>
</StackLayout>
<Image
Grid.Column="2"
Source="ic_chevron_right"
WidthRequest="40">
</Image>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
using System.Collections.ObjectModel;
using System.Linq;
using MyLeasing.Common.Models;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class PropertiesViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private OwnerResponse _owner;
private TokenResponse _token;
private ObservableCollection<PetItemViewModel> _pets;
private bool _isRefreshing;
if (parameters.ContainsKey("token"))
{
_token = parameters.GetValue<TokenResponse>("token");
}
if (parameters.ContainsKey("owner"))
{
_owner = parameters.GetValue<OwnerResponse>("owner");
Pets = new ObservableCollection<PetItemViewModel>(_owner.Pets.Select(p => new PetItemViewModel
{
Born = p.Born,
Histories = p.Histories,
Id = p.Id,
ImageUrl = p.ImageUrl,
Name = p.Name,
PetType = p.PetType,
Race = p.Race,
Remarks = p.Remarks
}).ToList());
}
IsRefreshing = false;
}
}
}
using MyLeasing.Common.Models;
namespace MyLeasing.Prism.ViewModels
{
public class ContractItemViewModel : ContractResponse
{
}
}
using MyLeasing.Common.Models;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class PropertyItemViewModel : PropertyResponse
{
private readonly INavigationService _navigationService;
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.Property"
BackgroundColor="Silver"
Title="{Binding Title}">
<StackLayout
Padding="10">
<Frame
CornerRadius="20"
HasShadow="True">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
HeightRequest="20"
Source="ic_chevron_left"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PreviousImageCommand}"/>
</Image.GestureRecognizers>
</Image>
<Image
Grid.Column="1"
Source="{Binding Image}"
HeightRequest="300"
Aspect="AspectFill"/>
<Image
Grid.Column="2"
HeightRequest="20"
Source="ic_chevron_right"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NextImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Text="Property Type" FontAttributes="Bold"/>
<Label Grid.Column="1" Grid.Row="0" Text="{Binding Property.PropertyType}"/>
<Label Grid.Column="0" Grid.Row="1" Text="Neighborhood" FontAttributes="Bold"/>
<Label Grid.Column="1" Grid.Row="1" Text="{Binding Property.Neighborhood}"/>
<Label Grid.Column="0" Grid.Row="2" Text="Address" FontAttributes="Bold"/>
<Label Grid.Column="1" Grid.Row="2" Text="{Binding Property.Address}"/>
<Label Grid.Column="0" Grid.Row="3" Text="Price" FontAttributes="Bold"/>
<Label Grid.Column="1" Grid.Row="3" Text="{Binding Property.Price, StringFormat='{0:C2}'}"/>
<Label Grid.Column="0" Grid.Row="4" Text="Square Meters" FontAttributes="Bold"/>
</ContentPage>
using System.Collections.ObjectModel;
using System.Linq;
using MyLeasing.Common.Models;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class PropertyViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private PropertyResponse _property;
private ObservableCollection<ContractItemViewModel> _contracts;
private string _image;
private DelegateCommand _previousImageCommand;
private DelegateCommand _nextImageCommand;
private int _positionImage;
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
Title = Property.Neighborhood;
Contracts = new ObservableCollection<ContractItemViewModel>(Property.Contracts.Select(c => new
ContractItemViewModel
{
EndDate = c.EndDate,
Id = c.Id,
IsActive = c.IsActive,
Lessee = c.Lessee,
Price = c.Price,
Remarks = c.Remarks,
StartDate = c.StartDate
}).ToList());
Image = Property.FirstImage;
}
_positionImage += delta;
if (_positionImage < 0)
{
_positionImage = Property.PropertyImages.Count - 1;
}
Image = Property.PropertyImages.ToList()[_positionImage].ImageUrl;
}
}
}
using MyLeasing.Common.Models;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ContractItemViewModel : ContractResponse
{
private readonly INavigationService _navigationService;
private DelegateCommand _selectContractCommand;
};
<StackLayout
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
FontAttributes="Bold"
Text="Remarks"/>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding Contract.Remarks}"/>
<Label
Grid.Column="0"
Grid.Row="1"
FontAttributes="Bold"
Text="Start Date"/>
<Label
Grid.Column="1"
Grid.Row="1"
Text="{Binding Contract.StartDate, StringFormat='{0:yyyy/MM/dd}'}"/>
<Label
Grid.Column="0"
Grid.Row="2"
FontAttributes="Bold"
Text="End Date"/>
<Label
Grid.Column="1"
Grid.Row="2"
Text="{Binding Contract.EndDate, StringFormat='{0:yyyy/MM/dd}'}"/>
<Label
Grid.Column="0"
Grid.Row="3"
FontAttributes="Bold"
Text="Price"/>
<Label
Grid.Column="1"
Grid.Row="3"
Text="{Binding Contract.Price, StringFormat='{0:C2}'}"/>
<Label
Grid.Column="0"
Grid.Row="4"
FontAttributes="Bold"
Text="Lessee"/>
<Label
Grid.Column="1"
Grid.Row="4"
Text="{Binding Contract.Lessee.FullName}"/>
</Grid>
</StackLayout>
</ContentPage>
using MyLeasing.Common.Models;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ContractViewModel : ViewModelBase
{
private ContractResponse _contract;
if (parameters.ContainsKey("contract"))
{
Contract = parameters.GetValue<ContractResponse>("contract");
}
}
}
}
global::Xamarin.Forms.Forms.Init(this, bundle);
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
LoadApplication(new App(new AndroidInitializer()));
global::Xamarin.Forms.Forms.Init();
FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
LoadApplication(new App(new iOSInitializer()));
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyVet.Prism.Views.PetsPage"
BackgroundColor="Silver"
Title="{Binding Title}">
…
<ffimageloading:CachedImage
Grid.Column="0"
Source="{Binding ImageUrl}"
LoadingPlaceholder= "LoaderImage"
ErrorPlaceholder= "ErrorImage"
CacheDuration= "50"
RetryCount= "3"
RetryDelay= "600"
DownsampleToViewSize = "true"
WidthRequest="100"/>
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyVet.Prism.Views.PetPage"
Title="{Binding Title}">
<StackLayout
Padding="10">
<ffimageloading:CachedImage
Source="{Binding Pet.ImageUrl}"
LoadingPlaceholder= "LoaderImage"
ErrorPlaceholder= "ErrorImage"
CacheDuration= "50"
RetryCount= "3"
RetryDelay= "600"
DownsampleToViewSize = "true"
WidthRequest="250"/>
<Grid>
7. Test it.
Add SfRotator
1. Get a Sync Fusion license: https://help.syncfusion.com/common/essential-studio/licensing/license-key#xamarinforms.
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("MTM3Njg0QDMxMzcyZTMyMmUzMGUvQlg3Tnk5ODRGQ01pb
zNnWmEyWHdWcExaaUVOQ0FKODZGNDFpekRtd2M9");
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/LoginPage");
}
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
new SfRotatorRenderer();
LoadApplication(new App(new AndroidInitializer()));
}
<ScrollView>
<StackLayout
Padding="10">
<syncfusion:SfRotator
x:Name="rotator"
EnableAutoPlay="True"
EnableLooping="True"
NavigationDelay="5000"
HeightRequest="300"/>
<Grid>
using Plugin.Settings;
using Plugin.Settings.Abstractions;
namespace MyLeasing.Common.Helpers
{
public static class Settings
{
private const string _propertyImages = "PropertyImages";
private static readonly string _settingsDefault = string.Empty;
using Xamarin.Forms;
namespace MyLeasing.Prism.Models
{
public class CustomData : ContentPage
{
public CustomData()
{
}
Image = image;
}
using MyLeasing.Common.Helpers;
using MyLeasing.Common.Models;
using MyLeasing.Prism.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
using Xamarin.Forms;
namespace MyLeasing.Prism.Views
{
rotator.ItemsSource = GetDataSource();
rotator.ItemTemplate = imageTemplate;
}
return list;
}
}
}
<ScrollView>
<StackLayout
Padding="10">
<syncfusion:SfRotator
BackgroundColor="#ececec"
EnableAutoPlay="True"
EnableLooping="True"
HeightRequest="300"
ItemsSource="{Binding ImageCollection}"
NavigationDelay="5000"
NavigationDirection="Horizontal"
NavigationStripMode="Dots"
NavigationStripPosition="Bottom">
<syncfusion:SfRotator.ItemTemplate>
<DataTemplate>
<ffimageloading:CachedImage
Aspect="AspectFit"
CacheDuration= "50"
DownsampleToViewSize = "true"
ErrorPlaceholder= "ErrorImage"
HeightRequest="300"
LoadingPlaceholder= "LoaderImage"
RetryCount= "3"
RetryDelay= "600"
Source="{Binding Image}"/>
</DataTemplate>
</syncfusion:SfRotator.ItemTemplate>
</syncfusion:SfRotator>
<Grid>
namespace MyLeasing.Common.Models
{
public class RotatorModel
{
public string Image { get; set; }
}
}
using Xamarin.Forms;
namespace MyLeasing.Prism.Views
{
public partial class PropertyPage : ContentPage
{
public PropertyPage()
{
InitializeComponent();
}
}
}
using MyLeasing.Common.Models;
using Prism.Navigation;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MyLeasing.Prism.ViewModels
{
public class PropertyPageViewModel : ViewModelBase
{
private PropertyResponse _property;
private ObservableCollection<RotatorModel> _imageCollection;
public PropertyPageViewModel(
INavigationService navigationService) : base(navigationService)
{
Title = "Property";
}
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
Title = $"Property: {Property.Neighborhood}";
LoadImages();
}
}
7. Test it
<StackLayout
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
FontAttributes="Bold"
Text="Start Date"/>
<Label
Grid.Column="1"
FontAttributes="Bold"
Text="End Date"/>
<Label
Grid.Column="2"
FontAttributes="Bold"
Text="Lessee"/>
</Grid>
<ListView
HasUnevenRows="True"
ItemsSource="{Binding Contracts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectContractCommand}"/>
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Text="{Binding StartDateLocal, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="1"
Text="{Binding EndDateLocal, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="2"
Text="{Binding Lessee.FullName}"
VerticalOptions="Center"/>
<Image
Grid.Column="3"
Source="ic_more_vert"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
using MyLeasing.Common.Models;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ContractItemViewModel : ContractResponse
{
private readonly INavigationService _navigationService;
private DelegateCommand _selectContractCommand;
using MyLeasing.Common.Models;
using Prism.Navigation;
using System.Collections.ObjectModel;
using System.Linq;
namespace MyLeasing.Prism.ViewModels
{
public class ContractsPageViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private PropertyResponse _property;
private ObservableCollection<ContractItemViewModel> _contracts;
public ContractsPageViewModel(
INavigationService navigationService) : base(navigationService)
{
_navigationService = navigationService;
Title = "Contracts";
}
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
LoadContracts();
}
}
<ScrollView>
<StackLayout
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
FontAttributes="Bold"
Text="Price"/>
<Label
Grid.Row="0"
Grid.Column="1"
Text="{Binding Contract.Price, StringFormat='{0:C2}'}"/>
<Label
Grid.Row="1"
Grid.Column="0"
FontAttributes="Bold"
Text="Start Date"/>
<Label
Grid.Row="1"
Grid.Column="1"
Text="{Binding Contract.StartDateLocal, StringFormat='{0:yyyy/MM/dd}'}"/>
<Label
Grid.Row="2"
Grid.Column="0"
FontAttributes="Bold"
Text="End Date"/>
<Label
Grid.Row="2"
Grid.Column="1"
Text="{Binding Contract.EndDateLocal, StringFormat='{0:yyyy/MM/dd}'}"/>
<Label
Grid.Row="3"
Grid.Column="0"
FontAttributes="Bold"
Text="Is Active?"
VerticalOptions="Center"/>
<CheckBox
Grid.Row="3"
Grid.Column="1"
IsChecked="{Binding Contract.IsActive}"/>
<Label
Grid.Row="4"
Grid.Column="0"
FontAttributes="Bold"
Text="Lessee"/>
<Label
Grid.Row="4"
Grid.Column="1"
Text="{Binding Contract.Lessee.FullName}"/>
<Label
Grid.Row="5"
Grid.Column="0"
FontAttributes="Bold"
Text="Remarks"/>
<Label
Grid.Row="5"
Grid.Column="1"
Text="{Binding Contract.Remarks}"/>
</Grid>
</StackLayout>
</ScrollView>
</ContentPage>
using MyLeasing.Common.Models;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ContractPageViewModel : ViewModelBase
{
private ContractResponse _contract;
public ContractPageViewModel(
INavigationService navigationService) : base(navigationService)
{
Title = "Contract";
}
if (parameters.ContainsKey("contract"))
{
Contract = parameters.GetValue<ContractResponse>("contract");
Title = $"Contract to: {Contract.Lessee.FullName}";
}
}
}
}
7. Test it.
<TabbedPage.Children>
<local:PropertyPage />
<local:ContractsPage />
</TabbedPage.Children>
</TabbedPage>
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class PropertyTabbedPageViewModel : ViewModelBase
{
public PropertyTabbedPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Property";
}
}
}
using Plugin.Settings;
using Plugin.Settings.Abstractions;
namespace MyLeasing.Common.Helpers
{
public static class Settings
{
private const string _property = "Property";
private static readonly string _settingsDefault = string.Empty;
…
Delete the method on natigated to
...
public ContractsPageViewModel(
INavigationService navigationService) : base(navigationService)
{
_navigationService = navigationService;
Title = "Contracts";
Property = JsonConvert.DeserializeObject<PropertyResponse>(Settings.Property);
LoadContracts();
}
Add SfBusyIndicator
1. Add the NuGet Syncfusion.Xamarin.SfBusyIndicator to all mobile projects.
global::Xamarin.Forms.Forms.Init(this, bundle);
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
new SfBusyIndicatorRenderer();
LoadApplication(new App(new AndroidInitializer()));
global::Xamarin.Forms.Forms.Init();
FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
new SfBusyIndicatorRenderer();
LoadApplication(new App(new iOSInitializer()));
Title="{Binding Title}">
<ScrollView>
<AbsoluteLayout>
<StackLayout
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="10">
<Label
Text="Email"/>
…
<busyindicator:SfBusyIndicator
AnimationType="Gear"
AbsoluteLayout.LayoutBounds=".5,.5,.5,.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Silver"
HorizontalOptions="Center"
TextColor="White"
IsVisible="{Binding IsRunning}"
Title="Loading..."
VerticalOptions="Center"
ViewBoxWidth="80"
ViewBoxHeight="80" />
…
</StackLayout>
</AbsoluteLayout>
</ScrollView>
</ContentPage>
5. Test it.
GIT Workshop
https://www.youtube.com/watch?v=AqocDsE_32c
https://www.youtube.com/watch?v=CDeG4S-mJts
git checkout -b <nombre_de_la_nueva_rama> <hash_del_commit_al_que_quiere_volver>
git checkout -b old-state 0d1d7fc32
Redirect Pages
1. Create NotAuthorized method on AccountController:
public IActionResult NotAuthorized()
{
return View();
}
@{
ViewData["Title"] = "NotAuthorized";
}
<br />
<br />
<img src="~/images/gopher_head-min.png" />
<h2>You are not authorized to perform this action!</h2>
3. Modify Startup.cs to configure the Application Cookie Options (after cookies lines):
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/Account/NotAuthorized";
options.AccessDeniedPath = "/Account/NotAuthorized";
});
app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
@{
ViewData["Title"] = "Error404";
}
<br />
<br />
<img src="~/images/gopher_head-min.png" />
<h2>Sorry, page not found</h2>
7. Test it!.
Self-registration of users
1. Add this method to IUserHelper:
{
Address = view.Address,
Document = view.Document,
Email = view.Username,
FirstName = view.FirstName,
LastName = view.LastName,
PhoneNumber = view.PhoneNumber,
UserName = view.Username
};
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace MyLeasing.Web.Helpers
{
public interface ICombosHelper
{
IEnumerable<SelectListItem> GetComboLessees();
IEnumerable<SelectListItem> GetComboPropertyTypes();
IEnumerable<SelectListItem> GetComboRoles();
}
}
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
namespace MyLeasing.Web.Helpers
{
public class CombosHelper : ICombosHelper
{
private readonly DataContext _dataContext;
{
_dataContext = dataContext;
}
return list;
}
return list;
}
return list;
}
}
}
services.AddScoped<ICombosHelper, CombosHelper>();
{
Roles = _combosHelper.GetComboRoles()
};
return View(view);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var role = "Owner";
if (view.RoleId == 1)
{
role = "Lessee";
}
if (view.RoleId == 1)
{
var lessee = new Lessee
{
Contracts = new List<Contract>(),
User = user
};
_dataContext.Lessees.Add(lessee);
await _dataContext.SaveChangesAsync();
}
else
{
var owner = new Owner
{
Contracts = new List<Contract>(),
Properties = new List<Property>(),
User = user
};
_dataContext.Owners.Add(owner);
await _dataContext.SaveChangesAsync();
}
if (result2.Succeeded)
{
return RedirectToAction("Index", "Home");
}
return View(view);
}
@model MyLeasing.Web.Models.AddUserViewModel
@{
ViewData["Title"] = "Register";
}
<h2>Register</h2>
<h4>Owner</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username" class="control-label"></label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Document" class="control-label"></label>
<input asp-for="Document" class="form-control" />
<span asp-validation-for="Document" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PhoneNumber" class="control-label"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="RoleId" class="control-label"></label>
<select asp-for="RoleId" asp-items="Model.Roles" class="form-control"></select>
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PasswordConfirm" class="control-label"></label>
<input asp-for="PasswordConfirm" class="form-control" />
<span asp-validation-for="PasswordConfirm" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Register" class="btn btn-primary" />
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
<div class="form-group">
Modifying users
1. Add the ChangePasswordViewModel class:
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class ChangePasswordViewModel
{
[Display(Name = "Current password")]
[Required(ErrorMessage = "The field {0} is mandatory.")]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6, ErrorMessage = "The {0} field must contain between {2} and {1} characters.")]
public string OldPassword { get; set; }
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6, ErrorMessage = "The {0} field must contain between {2} and {1} characters.")]
[Compare("NewPassword")]
public string Confirm { get; set; }
}
}
FirstName = user.FirstName,
LastName = user.LastName,
PhoneNumber = user.PhoneNumber
};
return View(view);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUser(EditUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await _userHelper.GetUserByEmailAsync(User.Identity.Name);
user.Document = view.Document;
user.FirstName = view.FirstName;
user.LastName = view.LastName;
user.Address = view.Address;
user.PhoneNumber = view.PhoneNumber;
await _userHelper.UpdateUserAsync(user);
return RedirectToAction("Index", "Home");
}
return View(view);
}
@model MyLeasing.Web.Models.EditUserViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>User</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="ChangeUser">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Document" class="control-label"></label>
<input asp-for="Document" class="form-control" />
<span asp-validation-for="Document" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PhoneNumber" class="control-label"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-action="ChangePassword" class="btn btn-warning">Change Password</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
6. Test it.
[HttpPost]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userHelper.GetUserByEmailAsync(User.Identity.Name);
if (user != null)
{
var result = await _userHelper.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
return RedirectToAction("ChangeUser");
}
else
{
ModelState.AddModelError(string.Empty, result.Errors.FirstOrDefault().Description);
}
}
else
{
ModelState.AddModelError(string.Empty, "User no found.");
}
}
return View(model);
}
@model MyLeasing.Web.Models.ChangePasswordViewModel
@{
ViewData["Title"] = "Register";
}
<h2>Change Password</h2>
<div class="row">
<div class="col-md-4 offset-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<div class="form-group">
<label asp-for="OldPassword">Current password</label>
<input asp-for="OldPassword" type="password" class="form-control" />
<span asp-validation-for="OldPassword" class="text-warning"></span>
</div>
<div class="form-group">
<label asp-for="NewPassword">New password</label>
<input asp-for="NewPassword" type="password" class="form-control" />
<span asp-validation-for="NewPassword" class="text-warning"></span>
</div>
<div class="form-group">
<label asp-for="Confirm">Confirm</label>
<input asp-for="Confirm" type="password" class="form-control" />
<span asp-validation-for="Confirm" class="text-warning"></span>
</div>
<div class="form-group">
<input type="submit" value="Change password" class="btn btn-primary" />
<a asp-action="ChangeUser" class="btn btn-success">Back to user</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
9. Test it.
"Mail": {
"From": "[email protected]",
"Smtp": "smtp.gmail.com",
"Port": 587,
"Password": "yourpassword"
}
namespace MyLeasing.Web.Helpers
{
public interface IMailHelper
{
void SendMail(string to, string subject, string body);
}
}
using MailKit.Net.Smtp;
using Microsoft.Extensions.Configuration;
using MimeKit;
namespace MyLeasing.Web.Helpers
{
public class MailHelper : IMailHelper
{
private readonly IConfiguration _configuration;
{
_configuration = configuration;
}
services.AddScoped<IMailHelper, MailHelper>();
9. Modify the register post method (first inject the IMailHelper in AccountController):
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await AddUser(view);
if (user == null)
{
ModelState.AddModelError(string.Empty, "This email is already used.");
return View(view);
}
_dataContext.Owners.Add(owner);
await _dataContext.SaveChangesAsync();
return View(view);
}
return View(view);
}
<div class="text-success">
<p>
@ViewBag.Message
</p>
</div>
return NotFound();
}
return View();
}
@{
ViewData["Title"] = "Confirm email";
}
<h2>@ViewData["Title"]</h2>
<div>
<p>
Thank you for confirming your email. Now you can login into system.
</p>
</div>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await AddUser(view);
if (user == null)
{
ModelState.AddModelError(string.Empty, "This email is already used.");
return View(view);
_dataContext.Owners.Add(owner);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(view);
}
14. Drop the database (PM> drop-database) to ensure that all the users have a confirmed email.
private async Task<User> CheckUserAsync(string document, string firstName, string lastName, string email, string phone, string
address, string role)
{
var user = await _userHelper.GetUserByEmailAsync(email);
if (user == null)
{
user = new User
{
FirstName = firstName,
LastName = lastName,
Email = email,
UserName = email,
PhoneNumber = phone,
Address = address,
Document = document
};
return user;
}
Password Recovery
1. Modify the login view:
<div class="form-group">
<input type="submit" value="Login" class="btn btn-success" />
<a asp-action="Register" class="btn btn-primary">Register New User</a>
<a asp-action="RecoverPassword" class="btn btn-link">Forgot your password?</a>
</div>
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class RecoverPasswordViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Web.Models
{
public class ResetPasswordViewModel
{
[Required]
public string UserName { get; set; }
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "The {0} field must contain between {2} and {1} characters.")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "The {0} field must contain between {2} and {1} characters.")]
[DataType(DataType.Password)]
[Compare("Password")]
public string ConfirmPassword { get; set; }
[Required]
public string Token { get; set; }
}
}
[HttpPost]
public async Task<IActionResult> RecoverPassword(RecoverPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userHelper.GetUserByEmailAsync(model.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "The email doesn't correspont to a registered user.");
return View(model);
}
return View(model);
}
[HttpPost]
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
{
var user = await _userHelper.GetUserByEmailAsync(model.UserName);
if (user != null)
{
var result = await _userHelper.ResetPasswordAsync(user, model.Token, model.Password);
if (result.Succeeded)
{
ViewBag.Message = "Password reset successful.";
return View();
}
@model MyLeasing.Web.Models.RecoverPasswordViewModel
@{
ViewData["Title"] = "Recover Password";
}
<h2>Recover Password</h2>
<div class="row">
<div class="col-md-4 offset-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<div class="form-group">
<label asp-for="Email">Email</label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-warning"></span>
</div>
<div class="form-group">
<input type="submit" value="Recover password" class="btn btn-primary" />
<a asp-action="Login" class="btn btn-success">Back to login</a>
</div>
</form>
<div class="text-success">
<p>
@ViewBag.Message
</p>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@model MyLeasing.Web.Models.ResetPasswordViewModel
@{
ViewData["Title"] = "Reset Password";
}
<div class="row">
<div class="col-md-4 offset-md-4">
<form method="post">
<div asp-validation-summary="All"></div>
<input type="hidden" asp-for="Token" />
<div class="form-group">
<label asp-for="UserName">Email</label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-warning"></span>
</div>
<div class="form-group">
<label asp-for="Password">New password</label>
<input asp-for="Password" type="password" class="form-control" />
<span asp-validation-for="Password" class="text-warning"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword">Confirm</label>
<input asp-for="ConfirmPassword" type="password" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-warning"></span>
</div>
<div class="form-group">
<input type="submit" value="Reset password" class="btn btn-primary" />
</div>
</form>
<div class="text-success">
<p>
@ViewBag.Message
</p>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
8. Test it.
@model IEnumerable<MyLeasing.Web.Data.Entities.Owner>
@{
ViewData["Title"] = "Index";
}
<p>
<a asp-action="Create" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create New</a>
</p>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Owners</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Document)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.LastName)
</th>
<th>
</td>
<td>
@Html.DisplayFor(modelItem => item.User.PhoneNumber)
</td>
<td>
@Html.DisplayFor(modelItem => item.Pets.Count)
</td>
<td>
<a asp-action="Edit" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-
pencil"></i> </a>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Owners/Delete/' + item_to_delete;
});
});
</script>
}
2. Test it.
3. Following the example, do the same for Details, DetailsProperty and DetailsContract.
4. Homework: according to owners example, do the same for: property types, contracts and lessees.
5. Homework solved:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Controllers
{
[Authorize(Roles = "Manager")]
public class PropertyTypesController : Controller
{
private readonly DataContext _context;
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(PropertyType propertyType)
{
if (ModelState.IsValid)
{
_context.Add(propertyType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(propertyType);
}
return View(propertyType);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(PropertyType propertyType)
{
if (ModelState.IsValid)
{
try
{
_context.Update(propertyType);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!PropertyTypeExists(propertyType.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(propertyType);
}
if (propertyType.Properties.Count > 0)
{
//TODO: message
return RedirectToAction(nameof(Index));
}
_context.PropertyTypes.Remove(propertyType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
@model IEnumerable<MyLeasing.Web.Data.Entities.PropertyType>
@{
ViewData["Title"] = "Index";
}
<p>
<a asp-action="Create" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create New</a>
</p>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Property Types</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<a asp-action="Edit" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-
pencil"></i> </a>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!--Delete Item-->
<div class="modal fade" id="deleteDialog" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Item</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Do you want to delete the record?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="btnYesDelete">Delete</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/PropertyTypes/Delete/' + item_to_delete;
});
});
</script>
}
@model MyLeasing.Web.Data.Entities.PropertyType
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Property Type</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="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
</form>
</div>
</div>
@model MyLeasing.Web.Data.Entities.PropertyType
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Property Type</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Helpers;
using MyLeasing.Web.Models;
namespace MyLeasing.Web.Controllers
{
[Authorize(Roles = "Manager")]
public class LesseesController : Controller
{
private readonly DataContext _dataContext;
private readonly IUserHelper _userHelper;
private readonly IMailHelper _mailHelper;
public LesseesController(
DataContext dataContext,
IUserHelper userHelper,
IMailHelper mailHelper)
{
_dataContext = dataContext;
_userHelper = userHelper;
_mailHelper = mailHelper;
}
return NotFound();
}
return View(lessee);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await _userHelper.AddUser(view, "Lessee");
if (user == null)
{
ModelState.AddModelError(string.Empty, "This email is already used.");
return View(view);
_dataContext.Lessees.Add(lessee);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(view);
}
return NotFound();
}
return View(view);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditUserViewModel view)
{
if (ModelState.IsValid)
{
var lessee = await _dataContext.Lessees
.Include(o => o.User)
lessee.User.Document = view.Document;
lessee.User.FirstName = view.FirstName;
lessee.User.LastName = view.LastName;
lessee.User.Address = view.Address;
lessee.User.PhoneNumber = view.PhoneNumber;
await _userHelper.UpdateUserAsync(lessee.User);
return RedirectToAction(nameof(Index));
}
return View(view);
}
_dataContext.Lessees.Remove(lessee);
await _dataContext.SaveChangesAsync();
await _userHelper.DeleteUserAsync(lessee.User.Email);
return RedirectToAction(nameof(Index));
}
}
}
@model IEnumerable<MyLeasing.Web.Data.Entities.Lessee>
@{
ViewData["Title"] = "Index";
}
<p>
<a asp-action="Create" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create New</a>
</p>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Lessees</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
</td>
<td>
@Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.PhoneNumber)
</td>
<td>
@Html.DisplayFor(modelItem => item.Contracts.Count)
</td>
<td>
<a asp-action="Edit" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-
pencil"></i> </a>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Lessees/Delete/' + item_to_delete;
});
});
</script>
}
@model MyLeasing.Web.Models.AddUserViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Lessee</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="RoleId" />
<div class="form-group">
<label asp-for="Username" class="control-label"></label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<partial name="_User"/>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PasswordConfirm" class="control-label"></label>
<input asp-for="PasswordConfirm" class="form-control" />
<span asp-validation-for="PasswordConfirm" class="text-danger"></span>
</div>
<div class="form-group">
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@model MyLeasing.Web.Models.EditUserViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Lessee</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@model MyLeasing.Web.Data.Entities.Lessee
@{
ViewData["Title"] = "Details";
}
<div>
<h4>Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Document)
</dd>
<dt>
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Controllers
{
[Authorize(Roles = "Manager")]
public class PropertiesController : Controller
{
private readonly DataContext _dataContext;
return View(property);
}
}
}
@model IEnumerable<MyLeasing.Web.Data.Entities.Property>
@{
ViewData["Title"] = "Index";
}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Properties</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
Property Type
</th>
<th>
Owner
</th>
<th>
@Html.DisplayNameFor(model => model.Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.SquareMeters)
</th>
<th>
@Html.DisplayNameFor(model => model.IsAvailable)
</th>
<th>
Images
</th>
<th>
Contracts
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.PropertyType.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Owner.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Neighborhood)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.SquareMeters)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsAvailable)
</td>
<td>
@Html.DisplayFor(modelItem => item.Remarks)
</td>
<td>
@Html.DisplayFor(modelItem => item.PropertyImages.Count)
</td>
<td>
@Html.DisplayFor(modelItem => item.Contracts.Count)
</td>
<td>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
});
</script>
}
@model MyLeasing.Web.Data.Entities.Property
@{
ViewData["Title"] = "Details";
}
<div class="row">
<div class="col-md-6">
<div>
<h4>Owner</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Owner.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.Document)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.FirstName)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.FirstName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Owner.User.PhoneNumber)
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.PhoneNumber)
</dd>
</dl>
</div>
</div>
<div class="col-md-6">
<div>
<h4>Property Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SquareMeters)
</dt>
<dd>
@Html.DisplayFor(model => model.SquareMeters)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Rooms)
</dt>
<dd>
@Html.DisplayFor(model => model.Rooms)
</dd>
<dt>
@Html.DisplayNameFor(model => model.HasParkingLot)
</dt>
<dd>
@Html.DisplayFor(model => model.HasParkingLot)
</dd>
<dt>
@Html.DisplayNameFor(model => model.IsAvailable)
</dt>
<dd>
@Html.DisplayFor(model => model.IsAvailable)
</dd>
<dt>
Contracts
</dt>
<dd>
@Html.DisplayFor(model => model.Contracts.Count)
</dd>
</dl>
</div>
</div>
</div>
<div>
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
<hr />
<div class="row">
<div class="col-md-3">
<div>
@if (Model.PropertyImages.Count == 0)
{
<h5>Not images added yet.</h5>
}
else
{
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Images</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTableImages">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.PropertyImages.FirstOrDefault().ImageUrl)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.PropertyImages)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(item.ImageUrl))
{
<tr>
<th>
Lessee
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Remarks)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().EndDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().IsActive)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contracts)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Lessee.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTableImages').DataTable();
$('#MyTableContracts').DataTable();
});
</script>
}
Managers CRUD
1. Modify the ManagersController:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Helpers;
using MyLeasing.Web.Models;
namespace MyLeasing.Web.Controllers
{
[Authorize(Roles = "Manager")]
public class ManagersController : Controller
{
private readonly DataContext _dataContext;
private readonly IUserHelper _userHelper;
private readonly IMailHelper _mailHelper;
public ManagersController(
DataContext dataContext,
IUserHelper userHelper,
IMailHelper mailHelper)
{
_dataContext = dataContext;
_userHelper = userHelper;
_mailHelper = mailHelper;
}
return View(manager);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(AddUserViewModel view)
{
if (ModelState.IsValid)
{
var user = await _userHelper.AddUser(view, "Manager");
if (user == null)
{
ModelState.AddModelError(string.Empty, "This email is already used.");
return View(view);
}
_dataContext.Managers.Add(manager);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(view);
}
return View(view);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditUserViewModel view)
{
if (ModelState.IsValid)
{
var manager = await _dataContext.Managers
.Include(m => m.User)
.FirstOrDefaultAsync(o => o.Id == view.Id);
manager.User.Document = view.Document;
manager.User.FirstName = view.FirstName;
manager.User.LastName = view.LastName;
manager.User.Address = view.Address;
manager.User.PhoneNumber = view.PhoneNumber;
await _userHelper.UpdateUserAsync(manager.User);
return RedirectToAction(nameof(Index));
}
return View(view);
}
_dataContext.Managers.Remove(manager);
await _dataContext.SaveChangesAsync();
await _userHelper.DeleteUserAsync(manager.User.Email);
return RedirectToAction(nameof(Index));
}
}
}
@model IEnumerable<MyLeasing.Web.Data.Entities.Manager>
@{
ViewData["Title"] = "Index";
}
<p>
<a asp-action="Create" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create New</a>
</p>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Managers</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Document)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.Email)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstOrDefault().User.PhoneNumber)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.User.Document)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.User.PhoneNumber)
</td>
<td>
<a asp-action="Edit" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-
pencil"></i> </a>
<a asp-action="Details" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon glyphicon-list">
</i> </a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Managers/Delete/' + item_to_delete;
});
});
</script>
}
@model MyLeasing.Web.Data.Entities.Manager
@{
ViewData["Title"] = "Details";
}
<h2>Manager</h2>
<div>
<h4>Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Document)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.FirstName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.FirstName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.PhoneNumber)
</dt>
<dd>
@Html.DisplayFor(model => model.User.PhoneNumber)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning">Edit</a>
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
@model MyLeasing.Web.Models.AddUserViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Manager</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="RoleId" />
<div class="form-group">
<label asp-for="Username" class="control-label"></label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<partial name="_User"/>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PasswordConfirm" class="control-label"></label>
<input asp-for="PasswordConfirm" class="form-control" />
<span asp-validation-for="PasswordConfirm" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@model MyLeasing.Web.Models.EditUserViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Manager</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-action="Index" class="btn btn-success">Back to List</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
6. Test it.
...
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">My Leasing</a>
…
<p>© 2019 - My Leasing by Zulu</p>
…
2. Add some images related to a veterinary, I hardly recommend use 1200 x 400 pixels.
@{
ViewData["Title"] = "Home Page";
}
</div>
<div class="item">
<img src="~/images/Home/v2.jpg" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v3.jpg" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v4.jpg" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v5.png" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v6.jpg" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v7.jpg" class="img-responsive" />
</div>
<div class="item">
<img src="~/images/Home/v8.jpg" class="img-responsive" />
</div>
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<hr />
<div class="row">
<div class="col-md-2">
<a asp-action="SearchProperties" class="btn btn-primary">Search Properties</a>
</div>
@if (User.IsInRole("Owner") || User.IsInRole("Lessee"))
{
<div class="col-md-2">
<a asp-action="MyContracts" class="btn btn-warning">My Contracts</a>
</div>
}
@if (User.IsInRole("Owner"))
{
<div class="col-md-2">
<a asp-action="MyProperties" class="btn btn-success">My Properties</a>
</div>
}
</div>
4. Test it.
Users functionality
Search Properties
1. Add the method SearchProperties to HomeController:
@model IEnumerable<MyLeasing.Web.Data.Entities.Property>
@{
ViewData["Title"] = "Index";
}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Properties</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
Image
</th>
<th>
Property Type
</th>
<th>
@Html.DisplayNameFor(model => model.Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.SquareMeters)
</th>
<th>
@Html.DisplayNameFor(model => model.Rooms)
</th>
<th>
@Html.DisplayNameFor(model => model.HasParkingLot)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(item.FirstImage))
{
<img src="@Url.Content(item.FirstImage)" alt="Image" style="width:300px;height:300px;max-width: 100%;
height: auto;" />
}
</td>
<td>
@Html.DisplayFor(modelItem => item.PropertyType.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Neighborhood)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.SquareMeters)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rooms)
</td>
<td>
@Html.DisplayFor(modelItem => item.HasParkingLot)
</td>
<td>
<a asp-action="DetailsProperty" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon
glyphicon-list"> </i> </a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
});
</script>
}
3. Test it.
return View(property);
}
@model MyLeasing.Web.Data.Entities.Property
@{
ViewData["Title"] = "Details";
}
<div class="row">
<div class="col-md-6">
<div>
<h4>Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
Property Type
</dt>
<dd>
@Html.DisplayFor(model => model.PropertyType.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SquareMeters)
</dt>
<dd>
@Html.DisplayFor(model => model.SquareMeters)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Rooms)
</dt>
<dd>
@Html.DisplayFor(model => model.Rooms)
</dd>
<dt>
@Html.DisplayNameFor(model => model.HasParkingLot)
</dt>
<dd>
@Html.DisplayFor(model => model.HasParkingLot)
</dd>
<dt>
}
</div>
<div class="col-md-4">
@if (Model.PropertyImages.Count > 2)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[2].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
</div>
<div class="row">
<div class="col-md-4">
@if (Model.PropertyImages.Count > 3)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[3].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
<div class="col-md-4">
@if (Model.PropertyImages.Count > 4)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[4].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
<div class="col-md-4">
@if (Model.PropertyImages.Count > 5)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[5].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
</div>
<div class="row">
<div class="col-md-4">
@if (Model.PropertyImages.Count > 6)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[6].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
<div class="col-md-4">
@if (Model.PropertyImages.Count > 7)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[7].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
<div class="col-md-4">
@if (Model.PropertyImages.Count > 8)
{
<img src="@Url.Content(Model.PropertyImages.ToList()[8].ImageUrl)" alt="Image"
style="width:400px;height:400px;max-width: 100%; height: auto;" />
}
</div>
</div>
}
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
6. Test it.
My Properties
1. Add the method MyProperties for HomeCotroller:
[Authorize(Roles = "Owner")]
public async Task<IActionResult> MyProperties()
{
var owner = await _dataContext.Owners
.Include(o => o.User)
.Include(o => o.Contracts)
.Include(o => o.Properties)
.ThenInclude(p => p.PropertyType)
.Include(o => o.Properties)
.ThenInclude(p => p.PropertyImages)
.FirstOrDefaultAsync(o => o.User.UserName.ToLower().Equals(User.Identity.Name.ToLower()));
if (owner == null)
{
return NotFound();
}
return View(owner);
}
@model MyLeasing.Web.Data.Entities.Owner
@{
ViewData["Title"] = "Details";
}
<h2>Owner</h2>
<div>
<h4>Details</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.User.Document)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Document)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.FirstName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.FirstName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.User.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.User.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.User.PhoneNumber)
</dt>
<dd>
@Html.DisplayFor(model => model.User.PhoneNumber)
</dd>
<dt>
Properties
</dt>
<dd>
@Html.DisplayFor(model => model.Properties.Count)
</dd>
<dt>
Contracts
</dt>
<dd>
@Html.DisplayFor(model => model.Contracts.Count)
</dd>
</dl>
</div>
<div>
<a asp-action="AddProperty" asp-route-id="@Model.Id" class="btn btn-primary">Add Property</a>
<a asp-action="Index" class="btn btn-success">Back to Home</a>
</div>
<hr />
@if (Model.Properties.Count == 0)
{
<h5>Not properties added yet.</h5>
}
else
{
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Properties</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().PropertyType.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().SquareMeters)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Rooms)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Stratum)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().IsAvailable)
</th>
<th>
Images
</th>
<th>
Contracts
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Properties)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.PropertyType.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Neighborhood)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.SquareMeters)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rooms)
</td>
<td>
@Html.DisplayFor(modelItem => item.Stratum)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsAvailable)
</td>
<td>
@Html.DisplayFor(modelItem => item.PropertyImages.Count)
</td>
<td>
@Html.DisplayFor(modelItem => item.Contracts.Count)
</td>
<td>
<a asp-action="EditProperty" class="btn btn-default" asp-route-id="@item.Id"><i class="glyphicon
glyphicon-pencil"></i> </a>
<a asp-action="DetailsPropertyOwner" class="btn btn-default" asp-route-id="@item.Id"><i
class="glyphicon glyphicon-list"> </i> </a>
<button data-id="@item.Id" class="btn btn-danger deleteItem" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
// Delete item
var item_to_delete;
$('.deleteItem').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Home/DeleteProperty/' + item_to_delete;
});
});
</script>
}
3. Test it.
using System.Threading.Tasks;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Models;
namespace MyLeasing.Web.Helpers
{
public interface IConverterHelper
{
using System.Threading.Tasks;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Models;
namespace MyLeasing.Web.Helpers
{
public class ConverterHelper : IConverterHelper
{
private readonly DataContext _dataContext;
private readonly ICombosHelper _combosHelper;
public ConverterHelper(
DataContext dataContext,
ICombosHelper combosHelper)
{
_dataContext = dataContext;
_combosHelper = combosHelper;
}
Price = contract.Price,
Remarks = contract.Remarks,
StartDate = contract.StartDate,
Id = contract.Id,
Lessees = _combosHelper.GetComboLessees(),
PropertyId = contract.Property.Id
};
}
}
}
[Authorize(Roles = "Owner")]
public async Task<IActionResult> AddProperty(int? id)
{
if (id == null)
{
return NotFound();
}
PropertyTypes = _combosHelper.GetComboPropertyTypes()
};
return View(view);
}
[HttpPost]
public async Task<IActionResult> AddProperty(PropertyViewModel model)
{
if (ModelState.IsValid)
{
var property = await _converterHelper.ToPropertyAsync(model, true);
_dataContext.Properties.Add(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(MyProperties));
}
return View(model);
}
@model MyLeasing.Web.Models.PropertyViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="AddProperty">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="OwnerId" />
<div class="form-group">
<label asp-for="PropertyTypeId" class="control-label"></label>
<select asp-for="PropertyTypeId" asp-items="Model.PropertyTypes" class="form-control"></select>
<span asp-validation-for="PropertyTypeId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Neighborhood" class="control-label"></label>
<input asp-for="Neighborhood" class="form-control" />
<span asp-validation-for="Neighborhood" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SquareMeters" class="control-label"></label>
<div class="form-group">
<label asp-for="Rooms" class="control-label"></label>
<input asp-for="Rooms" class="form-control" />
<span asp-validation-for="Rooms" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Stratum" class="control-label"></label>
<input asp-for="Stratum" class="form-control" />
<span asp-validation-for="Stratum" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="HasParkingLot" /> @Html.DisplayNameFor(model => model.HasParkingLot)
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsAvailable" /> @Html.DisplayNameFor(model => model.IsAvailable)
</label>
</div>
</div>
<div class="form-group">
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="MyProperties" class="btn btn-success">Back to My Properties</a>
</div>
</form>
</div>
</div>
9. Test it.
[Authorize(Roles = "Owner")]
public async Task<IActionResult> EditProperty(int? id)
{
if (id == null)
{
return NotFound();
}
[HttpPost]
public async Task<IActionResult> EditProperty(PropertyViewModel view)
{
if (ModelState.IsValid)
{
var property = await _converterHelper.ToPropertyAsync(view, false);
_dataContext.Properties.Update(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(MyProperties));
}
return View(view);
}
@model MyLeasing.Web.Models.PropertyViewModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="EditProperty">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="OwnerId" />
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="PropertyTypeId" class="control-label"></label>
<select asp-for="PropertyTypeId" asp-items="Model.PropertyTypes" class="form-control"></select>
<span asp-validation-for="PropertyTypeId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Neighborhood" class="control-label"></label>
<input asp-for="Neighborhood" class="form-control" />
<span asp-validation-for="Neighborhood" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Address" class="control-label"></label>
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<div class="form-group">
<label asp-for="Rooms" class="control-label"></label>
<input asp-for="Rooms" class="form-control" />
<span asp-validation-for="Rooms" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Stratum" class="control-label"></label>
<input asp-for="Stratum" class="form-control" />
<span asp-validation-for="Stratum" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="HasParkingLot" /> @Html.DisplayNameFor(model => model.HasParkingLot)
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="IsAvailable" /> @Html.DisplayNameFor(model => model.IsAvailable)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="Remarks" class="control-label"></label>
<textarea asp-for="Remarks" class="form-control"></textarea>
<span asp-validation-for="Remarks" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-action="MyProperties" class="btn btn-success">Back to My Properties</a>
</div>
</form>
</div>
</div>
[Authorize(Roles = "Owner")]
public async Task<IActionResult> DetailsPropertyOwner(int? id)
{
if (id == null)
{
return NotFound();
}
return View(property);
}
@model MyLeasing.Web.Data.Entities.Property
@{
ViewData["Title"] = "Details";
}
<div class="row">
<div class="col-md-6">
<div>
<h4>Owner</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Owner.User.Document)
</dt>
<dd>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Neighborhood)
</dt>
<dd>
@Html.DisplayFor(model => model.Neighborhood)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Address)
</dt>
<dd>
@Html.DisplayFor(model => model.Address)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SquareMeters)
</dt>
<dd>
@Html.DisplayFor(model => model.SquareMeters)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Rooms)
</dt>
<dd>
@Html.DisplayFor(model => model.Rooms)
</dd>
<dt>
@Html.DisplayNameFor(model => model.HasParkingLot)
</dt>
<dd>
@Html.DisplayFor(model => model.HasParkingLot)
</dd>
<dt>
@Html.DisplayNameFor(model => model.IsAvailable)
</dt>
<dd>
@Html.DisplayFor(model => model.IsAvailable)
</dd>
<dt>
Contracts
</dt>
<dd>
@Html.DisplayFor(model => model.Contracts.Count)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Remarks)
</dt>
<dd>
@Html.DisplayFor(model => model.Remarks)
</dd>
</dl>
</div>
</div>
</div>
<div>
<a asp-action="EditProperty" asp-route-id="@Model.Id" class="btn btn-warning">Edit</a>
<a asp-action="AddImage" asp-route-id="@Model.Id" class="btn btn-primary">Add Image</a>
<a asp-action="MyProperties" class="btn btn-success">Back to My Properties</a>
</div>
<hr />
<div class="row">
<div class="col-md-3">
<div>
@if (Model.PropertyImages.Count == 0)
{
<h5>Not images added yet.</h5>
}
else
{
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Images</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTableImages">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.PropertyImages.FirstOrDefault().ImageUrl)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.PropertyImages)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(item.ImageUrl))
{
<img src="@Url.Content(item.ImageUrl)" alt="Image" style="width:200px;height:200px;max-
width: 100%; height: auto;" />
}
</td>
<td>
<button data-id="@item.Id" class="btn btn-danger deleteImage" data-toggle="modal" data-
target="#deleteDialog"><i class="glyphicon glyphicon-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
</div>
</div>
<div class="col-md-9">
<div>
@if (Model.Contracts.Count == 0)
{
<h5>Not contracts added yet.</h5>
}
else
{
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Contracts</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTableContracts">
<thead>
<tr>
<th>
Lessee
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Remarks)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().EndDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Contracts.FirstOrDefault().IsActive)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contracts)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Lessee.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Remarks)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDateLocal)
</td>
<td>
@Html.DisplayFor(modelItem => item.EndDateLocal)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsActive)
</td>
<td>
<a asp-action="DetailsContract" class="btn btn-default" asp-route-id="@item.Id"><i
class="glyphicon glyphicon-list"> </i> </a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
<!--Delete Item-->
<div class="modal fade" id="deleteDialog" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Item</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Do you want to delete the record?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="btnYesDelete">Delete</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTableImages').DataTable();
$('#MyTableContracts').DataTable();
// Delete item
var item_to_delete;
$('.deleteImage').click((e) => {
item_to_delete = e.currentTarget.dataset.id;
});
$("#btnYesDelete").click(function () {
window.location.href = '/Home/DeleteImage/' + item_to_delete;
});
});
</script>
}
[Authorize(Roles = "Owner")]
public async Task<IActionResult> AddImage(int? id)
{
if (id == null)
{
return NotFound();
}
return View(view);
}
[HttpPost]
public async Task<IActionResult> AddImage(PropertyImageViewModel view)
{
if (ModelState.IsValid)
{
var path = string.Empty;
path = Path.Combine(
Directory.GetCurrentDirectory(),
"wwwroot\\images\\Properties",
file);
path = $"~/images/Properties/{file}";
}
_dataContext.PropertyImages.Add(propertyImage);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsPropertyOwner)}/{view.Id}");
}
return View(view);
}
@model MyLeasing.Web.Models.PropertyImageViewModel
@{
ViewData["Title"] = "Create";
}
<h2>Add Image</h2>
<h4>Property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="AddImage" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="ImageFile" class="control-label"></label>
<input asp-for="ImageFile" class="form-control" type="file" />
<span asp-validation-for="ImageFile" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="DetailsPropertyOwner" asp-route-id="@Model.Id" class="btn btn-success">Back to Property</a>
</div>
</form>
</div>
</div>
[Authorize(Roles = "Owner")]
public async Task<IActionResult> DeleteImage(int? id)
{
if (id == null)
{
return NotFound();
}
if (propertyImage == null)
{
return NotFound();
}
_dataContext.PropertyImages.Remove(propertyImage);
await _dataContext.SaveChangesAsync();
return RedirectToAction($"{nameof(DetailsPropertyOwner)}/{propertyImage.Property.Id}");
}
[Authorize(Roles = "Owner")]
public async Task<IActionResult> DeleteProperty(int? id)
{
if (id == null)
{
return NotFound();
}
if (property.Contracts?.Count > 0)
{
return RedirectToAction(nameof(MyProperties));
}
_dataContext.Properties.Remove(property);
await _dataContext.SaveChangesAsync();
return RedirectToAction(nameof(MyProperties));
}
My Contracts
1. Add the method MyContracts for HomeCotroller:
@model IEnumerable<MyLeasing.Web.Data.Entities.Contract>
@{
ViewData["Title"] = "Index";
}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Contracts</h3>
</div>
<div class="panel-body">
<table class="table table-hover table-responsive table-striped" id="MyTable">
<thead>
<tr>
<th>
Owner
</th>
<th>
Lessee
</th>
<th>
Property Type
</th>
<th>
@Html.DisplayNameFor(model => model.Property.Neighborhood)
</th>
<th>
@Html.DisplayNameFor(model => model.Property.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.EndDate)
</th>
<th>
@Html.DisplayNameFor(model => model.IsActive)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Owner.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Lessee.User.FullNameWithDocument)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.PropertyType.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.Neighborhood)
</td>
<td>
@Html.DisplayFor(modelItem => item.Property.Address)
</td>
<td>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#MyTable').DataTable();
});
</script>
}
3. Test it.
return View(contract);
}
@model MyLeasing.Web.Data.Entities.Contract
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Contract</h4>
<hr />
<dl class="dl-horizontal">
<dt>
Owner
</dt>
<dd>
@Html.DisplayFor(model => model.Owner.User.FullNameWithDocument)
</dd>
<dt>
Lessee
</dt>
<dd>
@Html.DisplayFor(model => model.Lessee.User.FullNameWithDocument)
</dd>
<dt>
Property Type
</dt>
<dd>
@Html.DisplayFor(model => model.Property.PropertyType.Name)
</dd>
<dt>
<dd>
@Html.DisplayFor(model => model.IsActive)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Remarks)
</dt>
<dd>
@Html.DisplayFor(model => model.Remarks)
</dd>
</dl>
</div>
<div>
<a asp-action="MyContracts" class="btn btn-success">Back to My Contracts</a>
</div>
6. Test it.
GROUP4
Account
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Common.Models
{
public class UserRequest
{
[Required]
public string Document { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Address { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Phone { get; set; }
[Required]
[StringLength(20, MinimumLength = 6)]
public string Password { get; set; }
[Required]
public int RoleId { get; set; } // 1: Owner, 2: Lessee
}
}
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MyLeasing.Common.Models;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Helpers;
using System.Linq;
using System.Threading.Tasks;
namespace MyLeasing.Web.Controllers.API
{
[Route("api/[Controller]")]
public class AccountController : ControllerBase
{
private readonly DataContext _dataContext;
private readonly IUserHelper _userHelper;
private readonly IMailHelper _mailHelper;
public AccountController(
DataContext dataContext,
IUserHelper userHelper,
IMailHelper mailHelper)
{
_dataContext = dataContext;
_userHelper = userHelper;
_mailHelper = mailHelper;
}
[HttpPost]
public async Task<IActionResult> PostUser([FromBody] UserRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new Response<object>
{
IsSuccess = false,
Message = "Bad request"
});
}
return BadRequest(result.Errors.FirstOrDefault().Description);
}
if (request.RoleId == 1)
{
await _userHelper.AddUserToRoleAsync(userNew, "Owner");
_dataContext.Owners.Add(new Owner { User = userNew });
}
else
{
await _userHelper.AddUserToRoleAsync(userNew, "Lessee");
_dataContext.Lessees.Add(new Lessee { User = userNew });
}
await _dataContext.SaveChangesAsync();
Message = "A Confirmation email was sent. Please confirm your account and log into the App."
});
}
}
}
3. Test it on postman.
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Common.Models
{
public class EmailRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}
[HttpPost]
[Route("RecoverPassword")]
public async Task<IActionResult> RecoverPassword([FromBody] EmailRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new Response<object>
{
IsSuccess = false,
Message = "Bad request"
});
}
6. Test it on postman.
[HttpPut]
public async Task<IActionResult> PutUser([FromBody] UserRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
userEntity.FirstName = request.FirstName;
userEntity.LastName = request.LastName;
userEntity.Address = request.Address;
userEntity.PhoneNumber = request.Phone;
userEntity.Document = request.Phone;
8. Test it on postman.
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Common.Models
{
public class ChangePasswordRequest
{
[Required]
[StringLength(20, MinimumLength = 6)]
public string OldPassword { get; set; }
[Required]
[StringLength(20, MinimumLength = 6)]
public string NewPassword { get; set; }
[Required]
public string Email { get; set; }
}
}
[HttpPost]
[Route("ChangePassword")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new Response<object>
{
IsSuccess = false,
Message = "Bad request"
});
}
Owner
1. Modify the OwnerResponse model:
using System.Collections.Generic;
namespace MyLeasing.Common.Models
{
public class OwnerResponse
{
public int RoleId { get; set; }
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyLeasing.Common.Models;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
using MyLeasing.Web.Helpers;
namespace MyLeasing.Web.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class OwnersController : ControllerBase
{
private readonly DataContext _dataContext;
private readonly IUserHelper _userHelper;
public OwnersController(
DataContext dataContext,
IUserHelper userHelper)
{
_dataContext = dataContext;
_userHelper = userHelper;
[HttpPost]
[Route("GetOwnerByEmail")]
public async Task<IActionResult> GetOwner(EmailRequest emailRequest)
{
try
{
var user = await _userHelper.GetUserByEmailAsync(emailRequest.Email);
if (user == null)
{
return BadRequest("User not found.");
}
Id = pi.Id,
ImageUrl = pi.ImageFullPath
}).ToList(),
PropertyType = p.PropertyType.Name,
Remarks = p.Remarks,
Rooms = p.Rooms,
SquareMeters = p.SquareMeters,
Stratum = p.Stratum
}).ToList(),
Contracts = lessee.Contracts?.Select(c => new ContractResponse
{
EndDate = c.EndDate,
Id = c.Id,
IsActive = c.IsActive,
Price = c.Price,
Remarks = c.Remarks,
StartDate = c.StartDate
}).ToList()
};
return Ok(response);
}
return Ok(response);
}
FirstName = lessee.User.FirstName,
LastName = lessee.User.LastName,
PhoneNumber = lessee.User.PhoneNumber
};
}
}
}
3. Test it on postman.
Properties
1. Add the FilesHelper class:
using System.IO;
namespace MyLeasing.Common.Helpers
{
public class FilesHelper
{
public static bool UploadPhoto(MemoryStream stream, string folder, string name)
{
try
{
stream.Position = 0;
var path = Path.Combine(Directory.GetCurrentDirectory(), folder, name);
File.WriteAllBytes(path, stream.ToArray());
}
catch
{
return false;
}
return true;
}
}
}
using System.ComponentModel.DataAnnotations;
namespace MyLeasing.Common.Models
{
public class PropertyRequest
{
public int Id { get; set; }
[Required]
public string Neighborhood { get; set; }
[Required]
public string Address { get; set; }
[Required]
public decimal Price { get; set; }
[Required]
public int SquareMeters { get; set; }
[Required]
public int Rooms { get; set; }
[Required]
[Required]
public int PropertyTypeId { get; set; }
[Required]
public int OwnerId { get; set; }
}
}
namespace MyLeasing.Common.Models
{
public class ImageRequest
{
public int Id { get; set; }
}
}
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyLeasing.Common.Helpers;
using MyLeasing.Common.Models;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Controllers.API
{
[Route("api/[Controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class PropertiesController : ControllerBase
{
private readonly DataContext _dataContext;
[HttpPost]
public async Task<IActionResult> PostProperty([FromBody] PropertyRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
_dataContext.Properties.Add(property);
await _dataContext.SaveChangesAsync();
return Ok(property);
[HttpPost]
[Route("AddImageToProperty")]
public async Task<IActionResult> AddImageToProperty([FromBody] ImageRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (response)
{
imageUrl = fullPath;
}
}
_dataContext.PropertyImages.Add(propertyImage);
await _dataContext.SaveChangesAsync();
return Ok(propertyImage);
}
[HttpPut("{id}")]
public async Task<IActionResult> PutProperty([FromRoute] int id, [FromBody] PropertyRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != request.Id)
{
return BadRequest();
}
oldProperty.Address = request.Address;
oldProperty.HasParkingLot = request.HasParkingLot;
oldProperty.IsAvailable = request.IsAvailable;
oldProperty.Neighborhood = request.Neighborhood;
oldProperty.Price = request.Price;
oldProperty.PropertyType = propertyType;
oldProperty.Remarks = request.Remarks;
oldProperty.Rooms = request.Rooms;
oldProperty.SquareMeters = request.SquareMeters;
oldProperty.Stratum = request.Stratum;
_dataContext.Properties.Update(oldProperty);
await _dataContext.SaveChangesAsync();
return Ok(oldProperty);
}
[HttpPost]
[Route("DeleteImageToProperty")]
public async Task<IActionResult> DeleteImageToProperty([FromBody] ImageRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (propertyImage == null)
{
return BadRequest("Property image doesn't exist.");
}
_dataContext.PropertyImages.Remove(propertyImage);
await _dataContext.SaveChangesAsync();
return Ok(propertyImage);
}
}
}
7. Test it on postman.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyLeasing.Web.Data;
using MyLeasing.Web.Data.Entities;
namespace MyLeasing.Web.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class PropertyTypesController : Controller
{
private readonly DataContext _dataContext;
[HttpGet]
public IEnumerable<PropertyType> GetPropertyTypes()
{
return _dataContext.PropertyTypes.OrderBy(pt => pt.Name);
}
}
}
9. Test it on postman.
using Plugin.Settings;
using Plugin.Settings.Abstractions;
namespace MyLeasing.Common.Helpers
{
4. Delete the method OnNavigatedTo and load the date on PropertiesViewModel constructor:
_token = JsonConvert.DeserializeObject<TokenResponse>(Settings.Token);
_owner = JsonConvert.DeserializeObject<OwnerResponse>(Settings.Owner);
IsRefreshing = false;
}
5. Test it.
namespace MyLeasing.Common.Models
{
public class Menu
{
public string Icon { get; set; }
using MyLeasing.Common.Models;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class MenuItemViewModel : Menu
{
await _navigationService.NavigateAsync($"/LeasingMasterDetailPage/NavigationPage/{PageName}");
}
}
}
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.LeasingMasterDetailPage"
BackgroundColor="Gray">
<MasterDetailPage.Master>
<ContentPage Title="Menu">
<StackLayout Padding="20">
<Image
HeightRequest="150"
Source="logo"/>
<ListView
BackgroundColor="Transparent"
ItemsSource="{Binding Menus}"
HasUnevenRows="True"
SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectMenuCommand}"/>
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
HeightRequest="50"
Source="{Binding Icon}"
WidthRequest="50"/>
<Label
Grid.Column="1"
FontAttributes="Bold"
VerticalOptions="Center"
TextColor="White"
Text="{Binding Title}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
</MasterDetailPage>
using MyLeasing.Common.Models;
using Prism.Navigation;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace MyLeasing.Prism.ViewModels
{
public class LeasingMasterDetailPageViewModel : ViewModelBase
{
new Menu
{
Icon = "ic_action_list_alt",
PageName = "ContractsPage",
Title = "Contracts"
},
new Menu
{
Icon = "ic_action_person",
PageName = "ModifyUserPage",
new Menu
{
Icon = "ic_action_map",
PageName = "MapPage",
Title = "Map"
},
new Menu
{
Icon = "ic_action_exit_to_app",
PageName = "LoginPage",
Title = "Log out"
}
};
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.MapPage"
Title="{Binding Title}">
</ContentPage>
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class MapPageViewModel : ViewModelBase
{
public MapPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Map";
}
}
}
Title="{Binding Title}">
</ContentPage>
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ModifyUserPageViewModel : ViewModelBase
{
public ModifyUserPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Modify User";
}
}
}
</style>
<style name="Theme.Splash" parent="android:Theme">
<item name="android:windowBackground">@drawable/leasing_splash</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>
using Android.App;
using Android.OS;
namespace MyLeasing.Prism.Droid
{
[Activity(
Theme = "@style/Theme.Splash",
MainLauncher = true,
NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
System.Threading.Thread.Sleep(1800);
StartActivity(typeof(MainActivity));
}
}
}
[Activity(
Label = "My Leasing",
Icon = "@mipmap/ic_launcher",
Theme = "@style/MainTheme",
MainLauncher = false,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
5. Test it.
6. Now add the icon launcher. Go to https://romannurik.github.io/AndroidAssetStudio/ and personalizate your own icon launcher.
And add the image to Android and iOS projects.
Adding Styles
1. Add those colors to dictionary:
<ResourceDictionary>
</ResourceDictionary>
Note: I recommend https://color.adobe.com/es/explore to get valid color combinations. In my example I’ve used:
https://color.adobe.com/es/explore?page=2
<ScrollView>
<AbsoluteLayout>
<StackLayout
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="10">
<Image
Margin="20"
Source="logo"
WidthRequest="230"/>
<Label
TextColor="{StaticResource colorFont}"
Text="Email"/>
<Entry
Keyboard="Email"
Placeholder="Enter your email..."
Text="{Binding Email}"/>
<Label
TextColor="{StaticResource colorFont}"
Text="Password"/>
<Entry
IsPassword="True"
Placeholder="Enter your password..."
Text="{Binding Password}"/>
<StackLayout
VerticalOptions="EndAndExpand">
<Button
BackgroundColor="{StaticResource colorPrimary}"
Command="{Binding LoginCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Login"
TextColor="{StaticResource colorFontInverse}"/>
</StackLayout>
<busyindicator:SfBusyIndicator
AnimationType="Gear"
AbsoluteLayout.LayoutBounds=".5,.5,.5,.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="{StaticResource colorSecondary}"
HorizontalOptions="Center"
TextColor="{StaticResource colorFontInverse}"
IsVisible="{Binding IsRunning}"
Title="Loading..."
VerticalOptions="Center"
ViewBoxWidth="80"
ViewBoxHeight="80" />
</AbsoluteLayout>
</ScrollView>
</ContentPage>
<item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
</style>
</resources>
4. Test it.
<Style TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource colorFont}" />
</Style>
<Button
Command="{Binding LoginCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Login"/>
7. Test it.
<StackLayout
Orientation="Horizontal"
VerticalOptions="EndAndExpand">
<Button
Command="{Binding LoginCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Login"/>
<Button
Command="{Binding RegisterCommand}"
IsEnabled="{Binding IsEnabled}"
Style="{StaticResource secondaryButton}"
Text="Register"/>
</StackLayout>
<Entry
IsPassword="True"
Placeholder="Enter your password..."
Text="{Binding Password}"/>
<StackLayout
HorizontalOptions="Center"
Orientation="Horizontal">
<Label
Text="Rememberme in this device"
VerticalOptions="Center"/>
<CheckBox
IsChecked="{Binding IsRemember}"/>
</StackLayout>
<Label
HorizontalOptions="Center"
FontAttributes="Bold"
Text="Forgot your password?"
TextColor="{StaticResource colorPrimary}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ForgotPasswordCommand}"/>
</Label.GestureRecognizers>
</Label>
<StackLayout
Orientation="Horizontal"
VerticalOptions="EndAndExpand">
…
private bool _isRemember;
…
public bool IsRemember
{
get => _isRemember;
set => SetProperty(ref _isRemember, value);
}
…
public LoginViewModel(
INavigationService navigationService,
IApiService apiService) : base(navigationService)
{
_navigationService = navigationService;
_apiService = apiService;
Title = "Login";
IsEnabled = true;
IsRemember = true;
2. In Prism project add the folder Resources and inside it, add the resource call Resource, add some literals and translate with
the ResX Manager tool. The default resource language must be Public, the others in no code generation.
3. In common project add the folder Interfaces, inside it, add the interface ILocalize.
using System.Globalization;
namespace MyLeasing.Prism.Interfaces
{
public interface ILocalize
{
CultureInfo GetCurrentCultureInfo();
using System;
namespace MyLeasing.Prism.Helpers
{
public class PlatformCulture
{
public string PlatformString { get; private set; }
5. In Prism project add folder Helpers and add the class Languages with the literals.
using MyLeasing.Prism.Interfaces;
using MyLeasing.Prism.Resources;
using Xamarin.Forms;
namespace MyLeasing.Prism.Helpers
{
public static class Languages
{
static Languages()
{
var ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
Resource.Culture = ci;
DependencyService.Get<ILocalize>().SetLocale(ci);
}
using Foundation;
using MyVet.Prism.Helpers;
using MyVet.Prism.Interfaces;
using System.Globalization;
using System.Threading;
using Xamarin.Forms;
[assembly: Dependency(typeof(MyLeasing.Prism.iOS.Implementations.Localize))]
namespace MyLeasing.Prism.iOS.Implementations
{
public class Localize : ILocalize
{
public CultureInfo GetCurrentCultureInfo()
{
var netLanguage = "en";
if (NSLocale.PreferredLanguages.Length > 0)
{
var pref = NSLocale.PreferredLanguages[0];
netLanguage = iOSToDotnetLanguage(pref);
}
// this gets called a lot - try/catch can be expensive so consider caching or something
CultureInfo ci = null;
try
{
ci = new System.Globalization.CultureInfo(netLanguage);
}
catch (CultureNotFoundException)
{
// iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
// fallback to first characters, in this case "en"
try
{
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
ci = new CultureInfo(fallback);
}
catch (CultureNotFoundException)
{
// iOS language not valid .NET culture, falling back to English
ci = new CultureInfo("en");
}
}
return ci;
}
Thread.CurrentThread.CurrentUICulture = ci;
}
return netLanguage;
}
return netLanguage;
}
}
}
<key>CFBundleLocalizations</key>
<array>
<string>es</string>
<string>pt</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
using System.Globalization;
using System.Threading;
using MyVet.Prism.Helpers;
using MyVet.Prism.Interfaces;
using Xamarin.Forms;
[assembly: Dependency(typeof(MyLeasing.Prism.Droid.Implementations.Localize))]
namespace MyLeasing.Prism.Droid.Implementations
{
public class Localize : ILocalize
{
public CultureInfo GetCurrentCultureInfo()
{
var netLanguage = "en";
var androidLocale = Java.Util.Locale.Default;
netLanguage = AndroidToDotnetLanguage(androidLocale.ToString().Replace("_", "-"));
// this gets called a lot - try/catch can be expensive so consider caching or something
CultureInfo ci = null;
try
{
ci = new CultureInfo(netLanguage);
}
catch (CultureNotFoundException)
{
// iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
// fallback to first characters, in this case "en"
try
{
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
ci = new CultureInfo(fallback);
}
catch (CultureNotFoundException)
{
// iOS language not valid .NET culture, falling back to English
ci = new CultureInfo("en");
}
}
return ci;
}
if (string.IsNullOrEmpty(Email))
{
11. Now to translate literals directly in the XAML add the class TranslateExtension in folder Helpers:
using System;
using System.Globalization;
using System.Reflection;
using System.Resources;
using MyVet.Prism.Interfaces;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyLeasing.Prism.Helpers
{
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
private readonly CultureInfo ci;
private const string ResourceId = "MyVet.Prism.Resources.Resource";
private static readonly Lazy<ResourceManager> ResMgr =
new Lazy<ResourceManager>(() => new ResourceManager(
ResourceId,
typeof(TranslateExtension).GetTypeInfo().Assembly));
public TranslateExtension()
{
ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
}
{
if (Text == null)
{
return "";
}
if (translation == null)
{
#if DEBUG
throw new ArgumentException(
string.Format(
"Key '{0}' was not found in resources '{1}' for culture '{2}'.",
Text, ResourceId, ci.Name), "Text");
#else
translation = Text; // returns the key, which GETS DISPLAYED TO THE USER
#endif
}
return translation;
}
}
}
using MyVet.Prism.Interfaces;
using MyVet.Prism.Resources;
using Xamarin.Forms;
namespace MyLeasing.Prism.Helpers
{
public static class Languages
{
static Languages()
{
var ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
Resource.Culture = ci;
DependencyService.Get<ILocalize>().SetLocale(ci);
}
Title = Languages.Login;
...
if (string.IsNullOrEmpty(Email))
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.EmailError, Languages.Accept);
return;
}
if (string.IsNullOrEmpty(Password))
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.PasswordError, Languages.Accept);
return;
}
…
if (!response.IsSuccess)
{
IsEnabled = true;
IsRunning = false;
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.LoginError, Languages.Accept);
Password = string.Empty;
return;
}
Text="{Binding Email}"/>
<Label
Text="{i18n:Translate Password}"/>
<Entry
IsPassword="True"
Placeholder="{i18n:Translate PasswordPlaceHolder}"
Text="{Binding Password}"/>
<StackLayout
HorizontalOptions="Center"
Orientation="Horizontal">
<Label
Text="{i18n:Translate Rememberme}"/>
<Switch
IsToggled="{Binding IsRemember}"/>
</StackLayout>
<Label
HorizontalOptions="Center"
Text="{i18n:Translate Forgot}"
TextColor="Navy">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ForgotPasswordCommand}"/>
</Label.GestureRecognizers>
</Label>
<ActivityIndicator
IsRunning="{Binding IsRunning}"
VerticalOptions="CenterAndExpand"/>
<StackLayout
Orientation="Horizontal">
<Button
Command="{Binding LoginCommand}"
HorizontalOptions="FillAndExpand"
IsEnabled="{Binding IsEnabled}"
Text="{i18n:Translate Login}"/>
<Button
Command="{Binding RegisterCommand}"
HorizontalOptions="FillAndExpand"
IsEnabled="{Binding IsEnabled}"
Style="{StaticResource SecondaryButton}"
Text="{i18n:Translate Register}"/>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage>
</ContentPage>
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class RegisterPageViewModel : ViewModelBase
{
…
private DelegateCommand _registerCommand;
…
public DelegateCommand RegisterCommand => _registerCommand ?? (_registerCommand = new DelegateCommand(Register));
…
private async void Register()
{
await _navigationService.NavigateAsync("Register");
}
<ScrollView>
<AbsoluteLayout>
<StackLayout
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Text="Document"
VerticalOptions="Center"/>
<Entry
Grid.Row="0"
Grid.Column="1"
Placeholder="Enter your document number..."
Text="{Binding Document}"/>
<Label
Grid.Row="1"
Grid.Column="0"
Text="Firstname"
VerticalOptions="Center"/>
<Entry
Grid.Row="1"
Grid.Column="1"
<Label
Grid.Row="2"
Grid.Column="0"
Text="Lastname"
VerticalOptions="Center"/>
<Entry
Grid.Row="2"
Grid.Column="1"
Placeholder="Enter your last name..."
Text="{Binding LastName}"/>
<Label
Grid.Row="3"
Grid.Column="0"
Text="Address"
VerticalOptions="Center"/>
<Entry
Grid.Row="3"
Grid.Column="1"
Placeholder="Enter your address..."
Text="{Binding Address}"/>
<Label
Grid.Row="4"
Grid.Column="0"
Text="Email"
VerticalOptions="Center"/>
<Entry
Grid.Row="4"
Grid.Column="1"
Keyboard="Email"
Placeholder="Enter your email..."
Text="{Binding Email}"/>
<Label
Grid.Row="5"
Grid.Column="0"
Text="Phone"
VerticalOptions="Center"/>
<Entry
Grid.Row="5"
Grid.Column="1"
Placeholder="Enter your phone number..."
Text="{Binding Phone}"/>
<Label
Grid.Row="6"
Grid.Column="0"
Text="Register as"
VerticalOptions="Center"/>
<Picker
Grid.Row="6"
Grid.Column="1"
ItemDisplayBinding="{Binding Name}"
ItemsSource="{Binding Roles}"
SelectedItem="{Binding Role}"
Title="Select a role...">
</Picker>
<Label
Grid.Row="7"
Grid.Column="0"
Text="Password"
VerticalOptions="Center"/>
<Entry
Grid.Row="7"
Grid.Column="1"
IsPassword="True"
Placeholder="Enter your password"
Text="{Binding Password}"/>
<Label
Grid.Row="8"
Grid.Column="0"
Text="Password Confirm"
VerticalOptions="Center"/>
<Entry
Grid.Row="8"
Grid.Column="1"
IsPassword="True"
Placeholder="Enter the password confirm..."
Text="{Binding PasswordConfirm}"/>
</Grid>
<Button
Command="{Binding RegisterCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Register"
VerticalOptions="EndAndExpand"/>
</StackLayout>
<busyindicator:SfBusyIndicator
AnimationType="Gear"
AbsoluteLayout.LayoutBounds=".5,.5,.5,.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="{StaticResource colorSecondary}"
HorizontalOptions="Center"
TextColor="{StaticResource colorFontInverse}"
IsVisible="{Binding IsRunning}"
Title="Registering..."
VerticalOptions="Center"
ViewBoxWidth="80"
ViewBoxHeight="80" />
</AbsoluteLayout>
</ScrollView>
</ContentPage>
using System;
using System.Net.Mail;
namespace MyLeasing.Common.Helpers
{
public static class RegexHelper
{
public static bool IsValidEmail(string emailaddress)
{
try
{
var mail = new MailAddress(emailaddress);
return true;
}
catch (FormatException)
{
return false;
}
}
}
}
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using MyLeasing.Common.Helpers;
using MyLeasing.Common.Models;
using MyLeasing.Prism.Helpers;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class RegisterPageViewModel : ViewModelBase
{
private bool _isRunning;
private bool _isEnabled;
private DelegateCommand _registerCommand;
if (string.IsNullOrEmpty(FirstName))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a firstname.", "Accept");
return false;
}
if (string.IsNullOrEmpty(LastName))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a lastname.", "Accept");
return false;
}
if (string.IsNullOrEmpty(Address))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter an address.", "Accept");
return false;
}
if (string.IsNullOrEmpty(Email) || !RegexHelper.IsValidEmail(Email))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter an email.", "Accept");
return false;
}
if (string.IsNullOrEmpty(Phone))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a phonenumber.", "Accept");
return false;
}
if (Role == null)
{
await App.Current.MainPage.DisplayAlert("Error", "You must seelct a role.", "Accept");
return false;
}
if (string.IsNullOrEmpty(Password))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a password.", "Accept");
return false;
}
if (Password.Length < 6)
{
await App.Current.MainPage.DisplayAlert("Error", "The password must have at least 6 charactes length.", "Accept");
return false;
}
if (string.IsNullOrEmpty(PasswordConfirm))
{
await App.Current.MainPage.DisplayAlert("Error", "You must enter a password confirm.", "Accept");
return false;
}
if (!Password.Equals(PasswordConfirm))
{
await App.Current.MainPage.DisplayAlert("Error", "The password and confirm does not match.", "Accept");
return false;
}
return true;
}
8. Test it.
Task<Response<object>> RegisterUserAsync(
string urlBase,
string servicePrefix,
string controller,
UserRequest userRequest);
}
catch (Exception ex)
{
return new Response<object>
{
IsSuccess = false,
Message = ex.Message,
};
}
}
IsRunning = true;
IsEnabled = false;
Phone = Phone,
RoleId = Role.Id
};
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert("Error", response.Message, "Accept");
return;
}
}
else
{
Title = "Available Properties";
}
</ContentPage>
using MyLeasing.Prism.Helpers;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class RememberPasswordViewModel : ViewModelBase
{
public RememberPasswordViewModel(INavigationService navigationService) : base(navigationService)
{
Title = Languages.PasswordRecover;
}
}
}
…
private DelegateCommand _forgotPasswordCommand;
…
public DelegateCommand ForgotPasswordCommand => _forgotPasswordCommand ?? (_forgotPasswordCommand = new
DelegateCommand(ForgotPassword));
…
private async void ForgotPassword()
{
await _navigationService.NavigateAsync("RememberPassword");
}
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.RememberPassword"
Title="{Binding Title}">
<ScrollView>
<StackLayout
Padding="10">
<Label
Text="{i18n:Translate Email}"/>
<Entry
Keyboard="Email"
Placeholder="{i18n:Translate EmailPlaceHolder}"
Text="{Binding Email}"/>
<ActivityIndicator
IsRunning="{Binding IsRunning}"
VerticalOptions="CenterAndExpand"/>
<Button
Command="{Binding RecoverCommand}"
IsEnabled="{Binding IsEnabled}"
Text="{i18n:Translate PasswordRecover}"/>
</StackLayout>
</ScrollView>
</ContentPage>
Task<Response> RecoverPasswordAsync(
string urlBase,
string servicePrefix,
string controller,
EmailRequest emailRequest);
using System.Threading.Tasks;
using MyLeasing.Common.Helpers;
using MyLeasing.Common.Models;
using MyLeasing.Common.Services;
using MyLeasing.Prism.Helpers;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class RememberPasswordPageViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private readonly IApiService _apiService;
private bool _isRunning;
private bool _isEnabled;
private DelegateCommand _recoverCommand;
public RememberPasswordPageViewModel(
INavigationService navigationService,
IApiService apiService) : base(navigationService)
{
_navigationService = navigationService;
_apiService = apiService;
Title = Languages.PasswordRecover;
IsEnabled = true;
}
IsRunning = true;
IsEnabled = false;
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(
Languages.Error,
response.Message,
Languages.Accept);
return;
}
await App.Current.MainPage.DisplayAlert(
Languages.Ok,
response.Message,
Languages.Accept);
await _navigationService.GoBackAsync();
}
return false;
}
return true;
}
}
}
Remember Me functionality
1. Modify the Settings class:
using Plugin.Settings;
using Plugin.Settings.Abstractions;
namespace MyLeasing.Common.Helpers
{
public static class Settings
{
private const string _token = "Token";
private const string _owner = "Owner";
private const string _isRemembered = "IsRemembered";
private static readonly string _stringDefault = string.Empty;
private static readonly bool _boolDefault = false;
Settings.Owner = JsonConvert.SerializeObject(owner);
Settings.Token = JsonConvert.SerializeObject(token);
Settings.IsRemembered = IsRemember;
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("MTU0OTIyQDMxMzcyZTMyMmUzMFR3RzVTejFPMTFkSmg3R
Tc2K2l3ZlBENkRKeTRlQXFEdmk3MnBLVWtYcUE9"); InitializeComponent();
if (PageName.Equals("Login"))
{
Settings.IsRemembered = false;
await _navigationService.NavigateAsync("/NavigationPage/Login");
return;
}
5. Test it.
</ContentPage>
using MyLeasing.Prism.Helpers;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ProfileViewModel : ViewModelBase
{
public ProfileViewModel(INavigationService navigationService) : base(navigationService)
{
Title = Languages.MyProfile;
}
}
}
new Menu
{
Icon = "ic_assignment_turned_in",
PageName = "Contrats",
Title = Languages.MyContracts
},
new Menu
{
Icon = "ic_map",
PageName = "Map",
Title = Languages.Map
},
new Menu
{
Icon = "ic_assignment_ind",
PageName = "Profile",
Title = Languages.MyProfile
},
new Menu
{
Icon = "ic_exit_to_app",
PageName = "Login",
Title = Languages.Logout
}
};
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:busyindicator="clr-namespace:Syncfusion.SfBusyIndicator.XForms;assembly=Syncfusion.SfBusyIndicator.XForms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.ModifyUserPage"
BackgroundColor="{StaticResource colorBackgroud}"
Title="{Binding Title}">
<ScrollView>
<AbsoluteLayout>
<StackLayout
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Text="Document"
VerticalOptions="Center"/>
<Entry
Grid.Row="0"
Grid.Column="1"
Placeholder="Document..."
Text="{Binding Owner.Document}"/>
<Label
Grid.Row="1"
Grid.Column="0"
Text="First Name"
VerticalOptions="Center"/>
<Entry
Grid.Row="1"
Grid.Column="1"
Placeholder="First Name..."
Text="{Binding Owner.FirstName}"/>
<Label
Grid.Row="2"
Grid.Column="0"
Text="Last Name"
VerticalOptions="Center"/>
<Entry
Grid.Row="2"
Grid.Column="1"
Placeholder="Last name..."
Text="{Binding Owner.LastName}"/>
<Label
Grid.Row="3"
Grid.Column="0"
Text="Address"
VerticalOptions="Center"/>
<Entry
Grid.Row="3"
Grid.Column="1"
Placeholder="Address..."
Text="{Binding Owner.Address}"/>
<Label
Grid.Row="4"
Grid.Column="0"
Text="Phone"
VerticalOptions="Center"/>
<Entry
Grid.Row="4"
Grid.Column="1"
Keyboard="Telephone"
Placeholder="Phonenumber..."
Text="{Binding Owner.PhoneNumber}"/>
</Grid>
<StackLayout
Orientation="Horizontal"
VerticalOptions="EndAndExpand">
<Button
Command="{Binding SaveCommand}"
HorizontalOptions="FillAndExpand"
IsEnabled="{Binding IsEnabled}"
Text="Save"/>
<Button
Command="{Binding ChangePasswordCommand}"
HorizontalOptions="FillAndExpand"
IsEnabled="{Binding IsEnabled}"
Style="{StaticResource secondaryButton}"
Text="Change Password"/>
</StackLayout>
</StackLayout>
<busyindicator:SfBusyIndicator
AnimationType="Gear"
AbsoluteLayout.LayoutBounds=".5,.5,.5,.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="{StaticResource colorDanger}"
HorizontalOptions="Center"
TextColor="{StaticResource colorFontInverse}"
IsVisible="{Binding IsRunning}"
Title="Saving..."
VerticalOptions="Center"
ViewBoxWidth="80"
ViewBoxHeight="80" />
</AbsoluteLayout>
</ScrollView>
</ContentPage>
using MyLeasing.Common.Helpers;
using MyLeasing.Common.Models;
using Newtonsoft.Json;
using Prism.Commands;
using Prism.Navigation;
using System.Threading.Tasks;
namespace MyLeasing.Prism.ViewModels
{
public class ModifyUserPageViewModel : ViewModelBase
{
private bool _isRunning;
private bool _isEnabled;
public ModifyUserPageViewModel(
INavigationService navigationService) : base(navigationService)
{
Title = "Modify User";
IsEnabled = true;
Owner = JsonConvert.DeserializeObject<OwnerResponse>(Settings.Owner);
}
{
var isValid = await ValidateDataAsync();
if (!isValid)
{
return;
}
}
if (string.IsNullOrEmpty(Owner.FirstName))
{
await App.Current.MainPage.DisplayAlert(
"Error",
"You must to enter a first name.",
"Accept");
return false;
}
if (string.IsNullOrEmpty(Owner.LastName))
{
await App.Current.MainPage.DisplayAlert(
"Error",
if (string.IsNullOrEmpty(Owner.Address))
{
await App.Current.MainPage.DisplayAlert(
"Error",
"You must to enter an address.",
"Accept");
return false;
}
return true;
}
}
}
Task<Response<object>> PutAsync<T>(
string urlBase,
string servicePrefix,
string controller,
T model,
string tokenType,
string accessToken);
IsRunning = true;
IsEnabled = false;
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept");
return;
}
Settings.Owner = JsonConvert.SerializeObject(Owner);
await App.Current.MainPage.DisplayAlert(
"Ok",
"User updated sucessfully.",
"Accept");
}
Task<Response<object>> ChangePasswordAsync(
string urlBase,
string servicePrefix,
string controller,
ChangePasswordRequest changePasswordRequest,
string tokenType,
string accessToken);
}
}
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:busyindicator="clr-namespace:Syncfusion.SfBusyIndicator.XForms;assembly=Syncfusion.SfBusyIndicator.XForms"
x:Class="MyLeasing.Prism.Views.ChangePasswordPage"
BackgroundColor="{StaticResource colorBackgroud}"
Title="{Binding Title}">
<ScrollView>
<AbsoluteLayout>
<StackLayout
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Text="Current Password"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="0"
IsPassword="True"
Placeholder="Current Password..."
Text="{Binding CurrentPassword}"/>
<Label
Grid.Column="0"
Grid.Row="1"
Text="New Password"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="1"
IsPassword="True"
Placeholder="New Password..."
Text="{Binding NewPassword}"/>
<Label
Grid.Column="0"
Grid.Row="2"
Text="Confirm New Password"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="2"
IsPassword="True"
Placeholder="Confirm New Password..."
Text="{Binding PasswordConfirm}"/>
</Grid>
<Button
Command="{Binding ChangePasswordCommand}"
IsEnabled="{Binding IsEnabled}"
Text="Change Password"
VerticalOptions="EndAndExpand"/>
</StackLayout>
<busyindicator:SfBusyIndicator
AnimationType="Gear"
AbsoluteLayout.LayoutBounds=".5,.5,.5,.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="{StaticResource colorDanger}"
HorizontalOptions="Center"
TextColor="{StaticResource colorFontInverse}"
IsVisible="{Binding IsRunning}"
Title="Saving..."
VerticalOptions="Center"
ViewBoxWidth="80"
ViewBoxHeight="80" />
</AbsoluteLayout>
</ScrollView>
</ContentPage>
using System.Threading.Tasks;
using MyLeasing.Common.Services;
using Prism.Commands;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class ChangePasswordPageViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private readonly IApiService _apiService;
public ChangePasswordPageViewModel(
INavigationService navigationService,
IApiService apiService) : base(navigationService)
{
_navigationService = navigationService;
_apiService = apiService;
IsEnabled = true;
Title = "Change Password";
}
if (string.IsNullOrEmpty(PasswordConfirm))
{
await App.Current.MainPage.DisplayAlert(
"Error",
"You must enter your a password confim.",
"Accept");
return false;
}
if (!NewPassword.Equals(PasswordConfirm))
{
await App.Current.MainPage.DisplayAlert(
"Error",
"The password and confirmation does not match.",
"Accept");
return false;
}
return true;
}
}
}
await _navigationService.NavigateAsync("ChangePassword");
}
7. Test it.
IsRunning = true;
IsEnabled = false;
"/Account/ChangePassword",
request,
"bearer",
token.Token);
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept");
return;
}
await App.Current.MainPage.DisplayAlert(
"Ok",
response.Message,
"Accept");
await _navigationService.GoBackAsync();
}
9. Test it.
App Property From App & Accessing Camera and Photo Library
1. Add the icon for toolbar ic_action_add_circle.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.Properties"
BackgroundColor="Silver"
Title="{Binding Title}">
<ContentPage.ToolbarItems>
<ToolbarItem Icon="ic_action_add_circle" Command="{Binding AddPropertyCommand}"/>
</ContentPage.ToolbarItems>
<StackLayout
await _navigationService.NavigateAsync("EditProperty");
}
</ContentPage>
using MyLeasing.Prism.Helpers;
using Prism.Navigation;
namespace MyLeasing.Prism.ViewModels
{
public class EditPropertyViewModel : ViewModelBase
{
public EditPropertyViewModel(INavigationService navigationService) : base(navigationService)
{
Title = Languages.NewProperty;
}
}
}
xmlns:i18n="clr-namespace:MyLeasing.Prism.Helpers"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyLeasing.Prism.Views.EditProperty"
Title="{Binding Title}">
<ScrollView>
<StackLayout
Padding="10">
<Image
HeightRequest="150"
Source="{Binding ImageSource}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ChangeImageCommand}"/>
</Image.GestureRecognizers>
</Image>
<Label
FontSize="Micro"
HorizontalOptions="Center"
Text="{i18n:Translate ChangeImage}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Text="{i18n:Translate Neighborhood}"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="0"
Placeholder="{i18n:Translate NeighborhoodPlaceHolder}"
Text="{Binding Property.Neighborhood}"/>
<Label
Grid.Column="0"
Grid.Row="1"
Text="{i18n:Translate Address}"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="1"
Placeholder="{i18n:Translate AddressPlaceHolder}"
Text="{Binding Property.Address}"/>
<Label
Grid.Column="0"
Grid.Row="2"
Text="{i18n:Translate Price}"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="2"
Keyboard="Numeric"
Placeholder="{i18n:Translate PricePlaceHolder}"
Text="{Binding Property.Price}"/>
<Label
Grid.Column="0"
Grid.Row="3"
Text="{i18n:Translate SquareMeters}"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="3"
Keyboard="Numeric"
Placeholder="{i18n:Translate SquareMetersPlaceHolder}"
Text="{Binding Property.SquareMeters}"/>
<Label
Grid.Column="0"
Grid.Row="4"
Text="{i18n:Translate Rooms}"
VerticalOptions="Center"/>
<Entry
Grid.Column="1"
Grid.Row="4"
Keyboard="Numeric"
Placeholder="{i18n:Translate RoomsPlaceHolder}"
Text="{Binding Property.Rooms}"/>
<Label
Grid.Column="0"
Grid.Row="5"
Text="{i18n:Translate Stratum}"
VerticalOptions="Center"/>
<Picker
Grid.Column="1"
Grid.Row="5"
ItemDisplayBinding="{Binding Name}"
ItemsSource="{Binding Stratums}"
SelectedItem="{Binding Stratum}"
Title="{i18n:Translate StratumPlaceHolder}"/>
<Label
Grid.Column="0"
Grid.Row="6"
Text="{i18n:Translate HasParkingLot}"
VerticalOptions="Center"/>
<Switch
Grid.Column="1"
Grid.Row="6"
IsToggled="{Binding Property.HasParkingLot}"/>
<Label
Grid.Column="0"
Grid.Row="7"
Text="{i18n:Translate IsAvailable}"
VerticalOptions="Center"/>
<Switch
Grid.Column="1"
Grid.Row="7"
IsToggled="{Binding Property.IsAvailable}"/>
<Label
Grid.Column="0"
Grid.Row="8"
Text="{i18n:Translate PropertyType}"
VerticalOptions="Center"/>
<Picker
Grid.Column="1"
Grid.Row="8"
ItemDisplayBinding="{Binding Name}"
ItemsSource="{Binding PropertyTypes}"
SelectedItem="{Binding PropertyType}"
Title="{i18n:Translate PropertyTypePlaceHolder}"/>
<Label
Grid.Column="0"
Grid.Row="9"
Text="{i18n:Translate Remarks}"
VerticalOptions="Center"/>
<Editor
Grid.Column="1"
Grid.Row="9"
HeightRequest="80"
Text="{Binding Property.Remarks}"/>
</Grid>
<ActivityIndicator
IsRunning="{Binding IsRunning}"
VerticalOptions="CenterAndExpand"/>
<StackLayout
Orientation="Horizontal">
<Button
Command="{Binding SaveCommand}"
IsEnabled="{Binding IsEnabled}"
Text="{i18n:Translate Save}"/>
<Button
Command="{Binding DeleteCommand}"
IsEnabled="{Binding IsEnabled}"
IsVisible="{Binding IsEdit}"
Style="{StaticResource DangerButton}"
Text="{i18n:Translate Delete}"/>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage>
using MyLeasing.Common.Models;
using MyLeasing.Prism.Helpers;
using Prism.Navigation;
using Xamarin.Forms;
namespace MyLeasing.Prism.ViewModels
{
public class EditPropertyPageViewModel : ViewModelBase
{
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
ImageSource = Property.FirstImage;
IsEdit = true;
Title = Languages.EditProperty;
}
else
{
Title = Languages.NewProperty;
Property = new PropertyResponse { IsAvailable = true };
ImageSource = "noImage";
IsEdit = false;
Title = Languages.NewProperty;
}
}
}
}
namespace MyLeasing.Common.Models
{
public class PropertyTypeResponse
{
public int Id { get; set; }
namespace MyLeasing.Common.Models
{
public class Stratum
{
public int Id { get; set; }
Task<Response> GetListAsync<T>(
string urlBase,
string servicePrefix,
string controller,
string tokenType,
string accessToken);
if (!response.IsSuccessStatusCode)
{
return new Response
{
IsSuccess = false,
Message = result,
};
}
IsEnabled = true;
}
…
public ObservableCollection<PropertyTypeResponse> PropertyTypes
{
get => _propertyTypes;
set => SetProperty(ref _propertyTypes, value);
}
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
ImageSource = Property.FirstImage;
IsEdit = true;
Title = Languages.EditProperty;
}
else
{
Title = Languages.NewProperty;
Property = new PropertyResponse { IsAvailable = true };
ImageSource = "noImage";
IsEdit = false;
Title = Languages.NewProperty;
}
LoadPropertyTypes();
LoadStratums();
}
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
if (!string.IsNullOrEmpty(Property.PropertyType))
{
PropertyType = PropertyTypes.FirstOrDefault(pt => pt.Name == Property.PropertyType);
}
}
<StackLayout
Padding="10">
<Frame
CornerRadius="20"
HasShadow="True">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
HeightRequest="20"
Source="ic_chevron_left"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PreviousImageCommand}"/>
</Image.GestureRecognizers>
</Image>
<Image
Grid.Column="1"
Source="{Binding Image}"
HeightRequest="300"
Aspect="AspectFill"/>
<Image
Grid.Column="2"
HeightRequest="20"
Source="ic_chevron_right"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NextImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Text="{i18n:Translate PropertyType}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding Property.PropertyType}"/>
<Label
Grid.Column="0"
Grid.Row="1"
Text="{i18n:Translate Neighborhood}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="1"
Text="{Binding Property.Neighborhood}"/>
<Label
Grid.Column="0"
Grid.Row="2"
Text="{i18n:Translate Address}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="2"
Text="{Binding Property.Address}"/>
<Label
Grid.Column="0"
Grid.Row="3"
Text="{i18n:Translate Price}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="3"
Text="{Binding Property.Price, StringFormat='{0:C2}'}"/>
<Label
Grid.Column="0"
Grid.Row="4"
Text="{i18n:Translate SquareMeters}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="4"
Text="{Binding Property.SquareMeters, StringFormat='{0:N2}'}"/>
<Label
Grid.Column="0"
Grid.Row="5"
Text="{i18n:Translate Rooms}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="5"
Text="{Binding Property.Rooms}"/>
<Label
Grid.Column="0"
Grid.Row="6"
Text="{i18n:Translate Remarks}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="6"
Text="{Binding Property.Remarks}"/>
</Grid>
</StackLayout>
</Frame>
<Button
Command="{Binding EditPropertyCommand}"
Text="{i18n:Translate EditProperty}"/>
<Label
FontAttributes="Bold"
FontSize="Large"
Text="{i18n:Translate Contracts}"
TextColor="Black"/>
<ListView
HasUnevenRows="True"
IsRefreshing="{Binding IsRefreshing}"
ItemsSource="{Binding Contracts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectContractCommand}"/>
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Text="{Binding StartDate, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="1"
Text="{Binding EndDate, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="2"
Text="{Binding Lessee.FullName}"
VerticalOptions="Center"/>
<Image
Grid.Column="3"
HeightRequest="20"
Margin="0,5"
Source="ic_chevron_right"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="ic_chevron_left"
VerticalOptions="Center"
WidthRequest="40"/>
<StackLayout
Grid.Column="1">
<Image
HeightRequest="150"
Source="{Binding ImageSource}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ChangeImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
<Image
Grid.Column="2"
Source="ic_chevron_right"
VerticalOptions="Center"
WidthRequest="40"/>
</Grid>
<StackLayout
HorizontalOptions="Center"
Orientation="Horizontal">
<Button
Command="{Binding AddImageCommand}"
IsVisible="{Binding IsEdit}"
Text="{i18n:Translate AddImage}"/>
<Button
Command="{Binding DeleteImageCommand}"
IsVisible="{Binding IsEdit}"
Style="{StaticResource DangerButton}"
Text="{i18n:Translate DeleteImage}"/>
</StackLayout>
26. Make a Commit (to be ensured to roll back) and update all the packages in all Xamarin projects:
29. Run the project again, to check everything is ok, and made a commit.
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Plugin.CurrentActivity;
using Plugin.Permissions;
using Prism;
using Prism.Ioc;
namespace MyLeasing.Prism.Droid
{
[Activity(
Label = "My Vet",
Icon = "@mipmap/ic_launcher",
Theme = "@style/MainTheme",
MainLauncher = false,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
CrossCurrentActivity.Current.Init(this, bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App(new AndroidInitializer()));
}
{
// Register any platform specific implementations
}
}
}
32. Add the folder xml inside Resources and inside it, add the file_paths.xml:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
<external-files-path name="my_movies" path="Movies" />
</paths>
<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs access to the photo gallery.</string>
if (source == Languages.Cancel)
{
_file = null;
return;
}
if (source == Languages.FromCamera)
{
_file = await CrossMedia.Current.TakePhotoAsync(
new StoreCameraMediaOptions
{
Directory = "Sample",
Name = "test.jpg",
PhotoSize = PhotoSize.Small,
}
);
}
else
{
_file = await CrossMedia.Current.PickPhotoAsync();
}
if (_file != null)
{
this.ImageSource = ImageSource.FromStream(() =>
{
var stream = _file.GetStream();
return stream;
});
}
}
37. Fix the Owners API controller to send the owner Id:
[HttpGet("GetLastPropertyByOwnerId/{id}")]
public async Task<IActionResult> GetLastPropertyByOwnerId([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (owner == null)
{
return NotFound();
}
return Ok(response);
}
Task<Response> PostAsync<T>(
string urlBase,
string servicePrefix,
string controller,
T model,
string tokenType,
string accessToken);
Task<Response> PutAsync<T>(
string urlBase,
string servicePrefix,
string controller,
int id,
T model,
string tokenType,
string accessToken);
Task<Response> GetLastPropertyByOwnerId(
string urlBase,
string servicePrefix,
string controller,
string tokenType,
string accessToken,
int ownerId);
string controller,
int id,
T model,
string tokenType,
string accessToken)
{
try
{
var request = JsonConvert.SerializeObject(model);
var content = new StringContent(request, Encoding.UTF8, "application/json");
var client = new HttpClient
{
BaseAddress = new Uri(urlBase)
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message,
};
}
}
if (!response.IsSuccessStatusCode)
{
return new Response
{
IsSuccess = false,
Message = result,
};
}
{
var isValid = await ValidateData();
if (!isValid)
{
return;
}
IsRunning = true;
IsEnabled = false;
Response response;
if (IsEdit)
{
response = await _apiService.PutAsync(url, "/api", "/Properties", propertyRequest.Id, propertyRequest, "bearer",
token.Token);
}
else
{
response = await _apiService.PostAsync(url, "/api", "/Properties", propertyRequest, "bearer", token.Token);
}
if (Property.Id != 0)
{
var imageRequest = new ImageRequest
{
PropertyId = Property.Id,
ImageArray = imageArray
};
if (!response.IsSuccess)
{
IsRunning = false;
IsEnabled = true;
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
await PropertiesViewModel.GetInstance().UpdateOwner();
IsRunning = false;
IsEnabled = true;
await App.Current.MainPage.DisplayAlert(
Languages.Error,
string.Format(Languages.CreateEditPropertyConfirm, IsEdit ? Languages.Edited : Languages.Created),
Languages.Accept);
await _navigationService.GoBackToRootAsync();
}
if (string.IsNullOrEmpty(Property.Address))
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.AddressError, Languages.Accept);
return false;
}
if (Property.Price <= 0)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.PriceError, Languages.Accept);
return false;
}
if (Property.SquareMeters <= 0)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.SquareMetersError, Languages.Accept);
return false;
}
if (Property.Rooms <= 0)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.RoomsError, Languages.Accept);
return false;
}
if (Stratum == null)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.StratumError, Languages.Accept);
return false;
}
if (PropertyType == null)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.PropertyTypeError, Languages.Accept);
return false;
}
return true;
}
return _instance;
}
if (response.IsSuccess)
{
var owner = (OwnerResponse)response.Result;
Settings.Owner = JsonConvert.SerializeObject(owner);
_owner = owner;
LoadProperties();
}
}
if (!response.IsSuccess)
{
IsRunning = false;
IsEnabled = true;
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
await PropertiesViewModel.GetInstance().UpdateOwner();
IsRunning = false;
IsEnabled = true;
await App.Current.MainPage.DisplayAlert(
Languages.Error,
string.Format(Languages.CreateEditPropertyConfirm, IsEdit ? Languages.Edited : Languages.Created),
Languages.Accept);
await _navigationService.GoBackToRootAsync();
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProperty([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return this.BadRequest(ModelState);
}
{
return this.NotFound();
}
if (property.Contracts.Count > 0)
{
BadRequest("The property can't be deleted because it has contracts.");
}
_dataContext.PropertyImages.RemoveRange(property.PropertyImages);
_dataContext.Properties.Remove(property);
await _dataContext.SaveChangesAsync();
return Ok("Property deleted");
}
Task<Response> DeleteAsync(
string urlBase,
string servicePrefix,
string controller,
int id,
string tokenType,
string accessToken);
};
}
if (!answer)
{
return;
}
IsRunning = true;
IsEnabled = false;
if (!response.IsSuccess)
{
IsRunning = false;
IsEnabled = true;
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
await PetsViewModel.GetInstance().UpdateOwner();
IsRunning = false;
IsEnabled = true;
await _navigationService.GoBackToRootAsync();
}
<ListView
HasUnevenRows="True"
SeparatorVisibility="None"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding RefreshPropertiesCommand}"
IsRefreshing="{Binding IsRefreshing}"
ItemsSource="{Binding Pets}">
58. To show the image a little bigger, modify the Property page:
BackgroundColor="Silver"
Title="{Binding Title}">
<StackLayout
Padding="10">
<Frame
CornerRadius="20"
HasShadow="True">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image
Grid.Column="0"
HeightRequest="40"
Source="ic_chevron_left"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PreviousImageCommand}"/>
</Image.GestureRecognizers>
</Image>
<Image
Grid.Column="1"
Source="{Binding Image}"
HeightRequest="150"
Aspect="AspectFill"/>
<Image
Grid.Column="2"
HeightRequest="40"
Source="ic_chevron_right"
VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NextImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Text="{i18n:Translate PropertyType}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding Property.PropertyType}"/>
<Label
Grid.Column="0"
Grid.Row="1"
Text="{i18n:Translate Neighborhood}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="1"
Text="{Binding Property.Neighborhood}"/>
<Label
Grid.Column="0"
Grid.Row="2"
Text="{i18n:Translate Address}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="2"
Text="{Binding Property.Address}"/>
<Label
Grid.Column="0"
Grid.Row="3"
Text="{i18n:Translate Price}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="3"
Text="{Binding Property.Price, StringFormat='{0:C2}'}"/>
<Label
Grid.Column="0"
Grid.Row="4"
Text="{i18n:Translate SquareMeters}"
FontAttributes="Bold"/>
<Label
Grid.Column="1"
Grid.Row="4"
Text="{Binding Property.SquareMeters, StringFormat='{0:N2}'}"/>
</Grid>
</StackLayout>
</Frame>
<Button
Command="{Binding EditPropertyCommand}"
Text="{i18n:Translate EditProperty}"/>
<Label
FontAttributes="Bold"
FontSize="Large"
Text="{i18n:Translate Contracts}"
TextColor="Black"/>
<ListView
HasUnevenRows="True"
IsRefreshing="{Binding IsRefreshing}"
ItemsSource="{Binding Contracts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectContractCommand}"/>
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Text="{Binding StartDate, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="1"
Text="{Binding EndDate, StringFormat='{0:yyyy/MM/dd}'}"
VerticalOptions="Center"/>
<Label
Grid.Column="2"
Text="{Binding Lessee.FullName}"
VerticalOptions="Center"/>
<Image
Grid.Column="3"
HeightRequest="20"
Margin="0,5"
Source="ic_chevron_right"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
59. Fix the method OnNavigatedTo on PropertyViewModel, to show the images for new properties:
if (parameters.ContainsKey("property"))
{
Property = parameters.GetValue<PropertyResponse>("property");
Title = Property.Neighborhood;
Image = Property.FirstImage;
if (Property.Contracts != null)
{
Contracts = new ObservableCollection<ContractItemViewModel>(Property.Contracts.Select(c => new
ContractItemViewModel(_navigationService)
{
EndDate = c.EndDate,
Id = c.Id,
IsActive = c.IsActive,
Lessee = c.Lessee,
Price = c.Price,
Remarks = c.Remarks,
StartDate = c.StartDate
}).ToList());
}
}
}
if (Property.Id == 0)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.AddImageError2, Languages.Accept);
return;
}
IsRunning = true;
IsEnabled = false;
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="ic_chevron_left"
VerticalOptions="Center"
WidthRequest="40">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PreviousImageCommand}"/>
</Image.GestureRecognizers>
</Image>
<StackLayout
Grid.Column="1">
<Image
HeightRequest="150"
Source="{Binding ImageSource}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ChangeImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
<Image
Grid.Column="2"
Source="ic_chevron_right"
VerticalOptions="Center"
WidthRequest="40">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NextImageCommand}"/>
</Image.GestureRecognizers>
</Image>
</Grid>
_positionImage += delta;
if (_positionImage < 0)
{
_positionImage = Property.PropertyImages.Count - 1;
}
ImageSource = Property.PropertyImages.ToList()[_positionImage].ImageUrl;
}
…
public DelegateCommand DeleteImageCommand => _deleteImageCommand ?? (_deleteImageCommand = new
DelegateCommand(DeleteImage));
…
private async void DeleteImage()
{
if (Property.PropertyImages.Count == 0)
{
return;
}
IsRunning = true;
IsEnabled = false;
IsRunning = false;
IsEnabled = true;
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
3. Add the NuGet Xam.Plugin.Geolocator to all mobility projects and Common project.
5. Modify the AndroidManifest.xml (replace the key for your own map key):
<key>NSLocationAlwaysUsageDescription</key>
<string>Can we use your location at all times?</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Can we use your location when your app is being used?</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Can we use your location at all times?</string>
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
CrossCurrentActivity.Current.Init(this, bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
Xamarin.FormsMaps.Init(this, bundle);
<StackLayout>
<maps:Map
x:Name="MyMap"
IsShowingUser="true"
MapType="Street"/>
</StackLayout>
</ContentPage>
using System.Threading.Tasks;
namespace MyLeasing.Common.Services
{
public interface IGeolocatorService
{
double Latitude { get; set; }
Task GetLocationAsync();
}
}
using System;
using System.Threading.Tasks;
using Plugin.Geolocator;
namespace MyLeasing.Common.Services
{
public class GeolocatorService : IGeolocatorService
{
public double Latitude { get; set; }
}
catch (Exception ex)
{
ex.ToString();
}
}
}
}
containerRegistry.RegisterForNavigation<Profile, ProfileViewModel>();
containerRegistry.RegisterForNavigation<ChangePassword, ChangePasswordViewModel>();
containerRegistry.RegisterForNavigation<EditPet, EditPetViewModel>();
containerRegistry.RegisterForNavigation<AssignModifyAgenda, AssignModifyAgendaViewModel>();
}
using MyLeasing.Common.Services;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace MyLeasing.Prism.Views
{
public partial class Map : ContentPage
{
private readonly IGeolocatorService _geolocatorService;
}
}
}
}
[DisplayFormat(DataFormatString = "{0:N6}")]
public double Latitude { get; set; }
[DisplayFormat(DataFormatString = "{0:N6}")]
public double Longitude { get; set; }
2. Save and run the command to add the new migration and update the database:
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().IsAvailable)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Latitude)
</th>
<th>
@Html.DisplayNameFor(model => model.Properties.FirstOrDefault().Longitude)
</th>
<th>
Images
</th>
…
<td>
@Html.DisplayFor(modelItem => item.IsAvailable)
</td>
<td>
@Html.DisplayFor(modelItem => item.Latitude)
</td>
<td>
@Html.DisplayFor(modelItem => item.Longitude)
</td>
<td>
@Html.DisplayFor(modelItem => item.PropertyImages.Count)
</td>
4. Modify the partial shared view _Property to add the new properties:
<div class="form-group">
<label asp-for="Remarks" class="control-label"></label>
<textarea asp-for="Remarks" class="form-control"></textarea>
<span asp-validation-for="Remarks" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Latitude" class="control-label"></label>
<input asp-for="Latitude" class="form-control" />
<span asp-validation-for="Latitude" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Longitude" class="control-label"></label>
<input asp-for="Longitude" class="form-control" />
<span asp-validation-for="Longitude" class="text-danger"></span>
</div>
Rooms = property.Rooms,
SquareMeters = property.SquareMeters,
Stratum = property.Stratum,
Owner = property.Owner,
OwnerId = property.Owner.Id,
PropertyType = property.PropertyType,
PropertyTypeId = property.PropertyType.Id,
PropertyTypes = _combosHelper.GetComboPropertyTypes(),
Remarks = property.Remarks,
Latitude = property.Latitude,
Longitude = property.Longitude
};
}
7. Test it, and set positions near to latitude 6 and longitude -75.
[HttpGet]
[Route("GetAvailbleProperties")]
public async Task<IActionResult> GetAvailbleProperties()
{
var properties = await _dataContext.Properties
.Include(p => p.PropertyType)
.Include(p => p.PropertyImages)
.Where(p => p.IsAvailable)
.ToListAsync();
return Ok(response);
}
{
InitializeComponent();
_geolocatorService = geolocatorService;
_apiService = apiService;
MoveMapToCurrentPositionAsync();
ShowOwners();
}
…
private async void ShowOwners()
{
var url = App.Current.Resources["UrlAPI"].ToString();
var token = JsonConvert.DeserializeObject<TokenResponse>(Settings.Token);
if (!response.IsSuccess)
{
await App.Current.MainPage.DisplayAlert(Languages.Error, response.Message, Languages.Accept);
return;
}
Document = Document,
Email = Email,
FirstName = FirstName,
LastName = LastName,
Password = Password,
Phone = Phone,
Latitude = _position.Latitude,
Longitude = _position.Longitude
};
…
if (string.IsNullOrEmpty(Address))
{
await App.Current.MainPage.DisplayAlert(Languages.Error, Languages.AddressError, Languages.Accept);
return false;
}
await _geolocatorService.GetLocationAsync();
if (_geolocatorService.Latitude != 0 && _geolocatorService.Longitude != 0)
{
_position = new Position(
_geolocatorService.Latitude,
_geolocatorService.Longitude);
if (locationList.Count == 1)
{
_position = locationList.FirstOrDefault();
return true;
}
if (locationList.Count > 1)
{
var addresses = new List<Address>();
Address = source;
var address = addresses.FirstOrDefault(a => a.Name == source);
_position = new Position(address.Latitude, address.Longitude);
}
return true;
}
Languages.NotLocationAvailable,
Languages.Accept);
return false;
}
}
else
{
return false;
}
if (locationList.Count == 1)
{
_position = locationList.FirstOrDefault();
return true;
}
if (locationList.Count > 1)
{
var addresses = new List<Address>();
var names = new List<string>();
foreach (var location in locationList)
{
var list = await geoCoder.GetAddressesForPositionAsync(location);
names.AddRange(list);
foreach (var item in list)
{
addresses.Add(new Address
{
Name = item,
Latitude = location.Latitude,
Longitude = location.Longitude
});
}
}
Owner.Address = source;
var address = addresses.FirstOrDefault(a => a.Name == source);
_position = new Position(address.Latitude, address.Longitude);
}
return true;
}