ASP.NET CORE Web API - Payment notification Email Scheduler using Hangfire Cron Job - asp.net-core

I have a Payment Application in ASP.NET Core-6 Web API Entity Framework. I have this model:
Payment:
public class Payment
{
public Guid Id { get; set; }
public string ReferenceNumber { get; set; }
public string Email { get; set; }
public DateTime TransactionDate { get; set; }
public DateTime? DueDate { get; set; }
public decimal Amount { get; set; }
}
EmailSettings:
public class EmailSettings
{
public string Username { get; set; }
public string Password { get; set; }
public string DisplayName { get; set; }
public string Host { get; set; }
public int Port { get; set; }
}
EmailService:
public async Task<string> SendEmailAsync(List<string> ToEmailName, string Subject, EventModel Data)
{
_mailResponse = string.Empty;
using (SmtpClient smtpClient = new SmtpClient(_mailConfig.Host, _mailConfig.Port))
{
smtpClient.UseDefaultCredentials = true;
smtpClient.Credentials = new NetworkCredential(_mailConfig.Username, _mailConfig.Password);
smtpClient.EnableSsl = true;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
smtpClient.SendCompleted += new SendCompletedEventHandler((object sender, AsyncCompletedEventArgs e) =>
{
_mailResponse = (e.Error != null || e.Cancelled != false) ? "failure" : "success";
});
MailMessage message = new MailMessage
{
From = new MailAddress(_mailConfig.Username, _mailConfig.DisplayName),
Subject = Subject,
SubjectEncoding = Encoding.UTF8,
BodyEncoding = Encoding.UTF8,
HeadersEncoding = Encoding.UTF8,
IsBodyHtml = true,
Body = GetEmailContent(Subject, Data),
Priority = MailPriority.High
};
foreach (string EmailName in ToEmailName)
{
message.To.Add(new MailAddress(EmailName));
}
await smtpClient.SendMailAsync(message);
}
return _mailResponse;
}
I am using HangFire.
I want the application to run a schedule using HangFire, and iterate the Payment Model using ReferenceNumber and DueDate. Then send email notification to the affected Email, 14 days to the DueDate. Reminding the affected users that his payment should be done on the DueDate
How do I achieve this?
Thanks

This can be done by scheduling notification directly based on DueDate - 14 days (or current date if that is less than 14 days away).
Also probably you would need to some extra check to your notification to see if that has already been paid before sending a reminder.
BackgroundJob.Schedule(
() => NotficationService.SendPaymentNotificationEmail(Payment.Id),
TimeSpan.FromDays(DueDate.AddDays(-14)));
Reference:
https://docs.hangfire.io/en/latest/background-methods/calling-methods-with-delay.html

Related

Why does my Web Api PUT using Entity Framework 6 keep writing new duplicate records rather than updating them?

I created my database in Entity Framework, and I also created a Web Api that uses Entity Framework. When I perform a GET or a POST (ADD) everything works great, but When I do a PUT (Update) my record is not updated, it is added as if I performed a Post. I think that the following does not recognize that the Entity has been modified:
db.Entry(contact).State = EntityState.Modified;
So, here is my entire Entity Contact.cs created by Entity Framework:
public partial class Contact
{
public int Contact_ID { get; set; }
public int Dataset_ID { get; set; }
public string Booth_UCID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Title_Role { get; set; }
public int Contact_Type_ID { get; set; }
public string Email { get; set; }
public string Phone_Number { get; set; }
public string Email_2 { get; set; }
public string Phone_Number_2 { get; set; }
public virtual Contact_Type Contact_Type { get; set; }
public virtual Dataset Dataset { get; set; }
}
Here is the Contact model from my application that is being sent to the Web Api:
public class Contact
{
public int Contact_ID { get; set; }
public int Dataset_ID { get; set; }
public string Booth_UCID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Title_Role { get; set; }
public int Contact_Type_ID { get; set; }
public string Email { get; set; }
public string Phone_Number { get; set; }
public string Email_2 { get; set; }
public string Phone_Number_2 { get; set; }
}
And here is my MVC Application to Edit Contact
[HttpPost]
public ActionResult EditContact(Contact contact)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:4251/");
//HTTP POST
// var postTask = client.PostAsJsonAsync<Dataset>("api/datasets/1", dataset);
var postTask = client.PostAsJsonAsync("api/contacts/2", contact);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
}
ModelState.AddModelError(string.Empty, "Server Error. Please contact administrator.");
return View(contact);
}
and lastly, here is my Web Api with the Entity Framework scafolding: this is straight out of the box, when I created my Web Api
// PUT: api/Contacts/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutContact(int id, Contact contact)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != contact.Contact_ID)
{
return BadRequest();
}
db.Entry(contact).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ContactExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I am at a loss as to what I could possible do. I feel like I should just abandon the Web Api with Entity Framework and just go ahead build an Empty Web Api where I control the update. And if so, how will this be different?
*** Update ***
I fixed this problem and I hope this helps others.
My issue was not within the Web Api or Entity Framework. My issue was in the Request that I was sending to the Web Api.
I wanted to do an Update (PUT), but when I ran this in debug I noticed the PUT method in my Web Api was not being triggered. I put a breakpoint on my POST method and that one was. So, I did a little research and I realized that I need to change the request below:
this line does a POST ADD, which is why I was duplicating my records in the database
var postTask = client.PostAsJsonAsync("api/datasets/2", dataset);
I changed it to the follow to do the Update:
var postTask = client.PutAsJsonAsync<Dataset>("api/datasets/2", dataset);
I thought that the uri I was sending would dictate which method put or post.

Net Core 2.1 Controller truncates Json while the json has no reference loops

I'm struggling with net core returning truncated response. I have already defined no reference loop in my startup services, and also tried to set compatibility version for the version i'm currently using 2.1 as follows:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
I also tried to serialize the array using JsonConvert and it did not throw any reference loop exception. Here's the action in the controller and the serializedArray text:
public IActionResult GetProductItems(int productId)
{
try
{
var productItems = _productsMethods.GetProductItems(productId);
// for testing the object for ref loops
string serialized = Newtonsoft.Json.JsonConvert.SerializeObject(productItems);
return Ok(productItems);
}
catch (ClientException ex)
{
return BadRequest(new { message = ex.Message });
}
catch (Exception ex)
{
return StatusCode(500, new { message = ex.Message });
}
}
// serialized string
//[{"ID":2,"ProductId":6,"ItemId":4,"Product":null,"Item":null,"Orders":[]},{"ID":3,"ProductId":":6,"ItemId":1,"Product":null,"Item":null,"Orders":[]},{"ID":5,"ProductId":":6,"ItemId":2,"Product":null,"Item":null,"Orders":[]}]
Here's the actual response
[{"id":2,"productId":6,"itemId":4,"product":null,"item":null,"orders":
Method:
public List<ProductItem> GetProductItems(int productId)
{
IQueryable<DataSets.ProductItem> query = db.ProductItems
.AsNoTracking()
.Include(k => k.Orders)
.Where(k => k.ProductId == productId);
// result truncated (when array orders is empty)
//return query.Select(_mapper.Map<ProductItem>).ToList();
// without automapper, also truncated
//return query.Select(k => new ProductItem()
//{
// ID = k.ID,
// ItemId = k.ItemId,
// ProductId = k.ProductId,
// Orders = k.Orders.Select(a => new Order() { ID = a.ID })
// .ToList()
//}).ToList();
// WORKS, not getting truncated
// order not included
return query.Select(k => new ProductItem()
{
ID = k.ID,
ItemId = k.ItemId,
ProductId = k.ProductId,
}).ToList();
}
Entities (renamed and removed props for simplification):
public class Product
{
public int ID { get; set; }
// some props
public string UserId { get; set; }
public User User { get; set; }
public List<ProductItem> Items { get; set; }
}
public class ProductItem
{
public int ID { get; set; }
// some props
public int ProductId { get; set; }
public int ItemId { get; set; }
public Product Product { get; set; }
public Item Item { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int ID { get; set; }
// some props
public int ItemId { get; set; }
public ProductItemOrder Item { get; set; }
}
Since there is no reference loop in orders and also the reference loop is ignored. Why is this still truncating?
I think the problem is public Product Product { get; set; } part here. You should define your Product entity virtually.
Here is an example below,
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Tags { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
Here is source.
For someone who might have this issue in the future. While I think the API should throw that error instead of just truncating the response.
I had 2 properties with the same letters but different letter case IPAddress and IpAddress.
SerializeObject alone wasn't throwing an exception, then I did this (CamelCase Resolver) to point out the issue:
Newtonsoft.Json.JsonConvert.DefaultSettings = () => new Newtonsoft.Json.JsonSerializerSettings
{
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
string serialized = Newtonsoft.Json.JsonConvert.SerializeObject(productItems);
So it threw: A member with the name 'ipAddress' already exists on ...

How to get all user with the associate organization?

Its keep a week that I'm trying to figure this out. I hope to get help from community. Here is a scenario:
I have a entity class called "Company". One company has many users (One-To-Many)
public class User : IdentityUser<int>
{
public User()
{
Company = new Company();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.Now;
public virtual ICollection<UserRole> UserRoles { get; set; }
public virtual Company Company { get; set; }
public int CompanyId { get; set; }
}
I also have a company entity like so.
public class Company
{
public Company()
{
Users = new List<User>();
}
public int CompanyId { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Company Name")]
public string CompanyName { get; set; }
public virtual List<User> Users { get; set; }
}
Now, I can add new company but I cannot add users.
Here is my Controller/Create
[HttpGet]
public IActionResult Create(int companyId)
{
ViewData["UserList"] = new SelectList(_context.Companies, "CompanyId", "CompanyName", companyId);
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RegisterViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(viewModel.UserName);
if (user == null)
{
user = new User
{
UserName = viewModel.UserName,
FirstName = viewModel.FirstName,
LastName = viewModel.LastName,
Email = viewModel.UserName,
PhoneNumber = viewModel.PhoneNumber
};
var result = await _userManager.CreateAsync(user, viewModel.Password);
if (result.Succeeded)
{
RedirectToAction(nameof(Index));
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View();
}
}
return View("Success");
}
ViewData["CompanyId"] = new SelectList(_context.Companies, "Id", "Id", viewModel.CompanyId);
return View();
}
When i run the program and enter data in a POST/Form,
if (ModelState.IsValid)
{
Model.IsValid is always return false. and it ask to enter Company information which i don't want it because i already have company data. I all was trying to do is have Foreign Id linked with user.
Since Identity already have built in function like
var users = await _userManager.GetUserAsync(viewModel.UserName);
How to i also query like Include in GetUserAsync method?

How can i change authentication type as phone number instead of user name on my web api?

My authentication is working fine on it is own but i need to use phoneNumber of users instead of user names.
There is my Provider class
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Identity.Providers
{
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "User did not confirm email.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
}
}
in this class context is coming with only userName and Password,so it cant reach PhoneNumber even i send it as a parameter.I think problem will solve after if i can change
userManager.FindAsync(context.UserName, context.Password)
like this
userManager.FindAsync(context.PhoneNumber, context.Password)
VS doesn't allow me to interfere OAuthGrantResourceOwnerCredentialsContext
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http.Routing;
namespace Identity.Models
{
public class ModelFactory
{
private UrlHelper _UrlHelper;
private ApplicationUserManager _AppUserManager;
public ModelFactory(HttpRequestMessage request, ApplicationUserManager appUserManager)
{
_UrlHelper = new UrlHelper(request);
_AppUserManager = appUserManager;
}
public UserReturnModel Create(ApplicationUser appUser)
{
return new UserReturnModel
{
Url = _UrlHelper.Link("GetUserById", new { id = appUser.Id }),
Id = appUser.Id,
UserName = appUser.UserName,
FullName = string.Format("{0} {1}", appUser.FirstName, appUser.LastName),
Email = appUser.Email,
EmailConfirmed = true,
Level = appUser.Level,
JoinDate = appUser.JoinDate,
Roles = _AppUserManager.GetRolesAsync(appUser.Id).Result,
Claims = _AppUserManager.GetClaimsAsync(appUser.Id).Result,
PhoneNumber = appUser.PhoneNumber
};
}
public RoleReturnModel Create(IdentityRole appRole)
{
return new RoleReturnModel
{
Url = _UrlHelper.Link("GetRoleById", new { id = appRole.Id }),
Id = appRole.Id,
Name = appRole.Name
};
}
}
public class RoleReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class UserReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public int Level { get; set; }
public DateTime JoinDate { get; set; }
public IList<string> Roles { get; set; }
public IList<System.Security.Claims.Claim> Claims { get; set; }
}
}
As result I stucked on authenticating with phoneNumber instead of userName and set deviceId as password
public override Task<ApplicationUser> FindAsync(string Phone, string password)
{
//Do your Stuff here
//return base.FindAsync(userName, password);
}
Overrride FIndAsync() in the IndentityConfig.cs

Entity Framework Creates New Record in Table I Didn't Reference when Inserting Into Other Table

In this website, users can register under a username and password, and can also post comments on articles. The models are pretty straightforward:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool IsAdmin { get; set; }
public DateTime JoinDate { get; set; }
public string AvatarPath { get; set; }
public string EmailAddress { get; set; }
}
public class ArticleComment
{
public int Id { get; set; }
public int ArticleId { get; set; }
public int UserId { get; set; }
public string CommenterName { get; set; }
public string Message { get; set; }
public DateTime CommentDate { get; set; }
public User User { get; set; }
}
Entity Framework correctly made the foreign key relationship between UserId on ArticleComment and Id on User when the database was created using code-first.
Here's my code for when a user posts a new comment:
public JsonResult SubmitComment(int articleId, string comment)
{
var response = new JsonResponse();
var currentUser = _userRepository.GetUserByUsername(User.Identity.Name);
//...
var newComment = new ArticleComment
{
ArticleId = articleId,
CommentDate = DateTime.Now,
CommenterName = currentUser.Username,
UserId = currentUser.Id,
User = currentUser,
Message = comment,
};
try
{
_articleRepository.Insert(newComment);
}
catch (Exception e)
{
response.Success = false;
response.AddError("newComment", "Sorry, we could not add your comment. Server error: " + e.Message);
return Json(response);
}
response.Success = true;
response.Value = newComment;
return Json(response);
}
The values that make up the newComment object all appear to be correct, and the Insert method in my Article repository class is straight and to the point:
public void Insert(ArticleComment input)
{
DataContext.ArticleComments.Add(input);
DataContext.SaveChanges();
}
But once this happens, poof: a new record in my Users table appears along with the new record in ArticleComments. All of the info in the new Users record is duplicated from that user's existing record - the only difference is the value for the primary key Id. What gives?
In addition to my comment, you need to make sure that both _userRepository and _articleRepository are using the same DbContext instance.
Either that, or you can try this:
var newComment = new ArticleComment
{
ArticleId = articleId,
CommentDate = DateTime.Now,
CommenterName = currentUser.Username,
UserId = currentUser.Id,
// User = currentUser, let the UserId figure out the User, don't set it yourself.
Message = comment,
};