Asked by:
Code review needed

Question
-
User-1104215994 posted
Hello guys,
Here is my asp.net web api controller. I would be glad if you can review and improve my code in terms of maintainability, refactoring, simplicity, incorrect/incomplete usage and etc. Please don't be cruel :)
[RoutePrefix("api/v2/pin")] public class InitiatesController : ApiController { private readonly EPINMiddleWareAPIContext context; public InitiatesController(EPINMiddleWareAPIContext context) { this.context = context; } // POST: api/Game //[RequireHttps] For Prod Only [HttpPost, Route("initiation")] public async Task<IHttpActionResult> PostInitiate(InitiateRequest initiate) { if (!ModelState.IsValid) { return BadRequest(ModelState); } #region Validate Company var responseMsg = new HttpResponseMessage(); var isUsernamePasswordValid = false; //Find Company in our database var companyCheck = await (context.Companies.Where(p => p.customerID == initiate.customerID)).SingleOrDefaultAsync(); //Check company password in our database if (companyCheck != null) isUsernamePasswordValid = Utilities.VerifyPassword(initiate.password, companyCheck.hash, companyCheck.salt); //Send UnAuthorized if we don't know the company if (!isUsernamePasswordValid) { // if credentials are not valid send unauthorized status code in response responseMsg.StatusCode = HttpStatusCode.Unauthorized; IHttpActionResult rMessage = ResponseMessage(responseMsg); return rMessage; } #endregion #region Check Code Bank database //Check Games in our database - all or nothing var gameBankResult = await (context.GameBanks.Where(g => g.productCode == initiate.productCode) .Where(l => l.referenceId == null) .Take(initiate.quantity)).ToListAsync(); //If we have games in our database, mark them! if (gameBankResult.Count() != 0) { foreach (var item in gameBankResult) { item.referenceId = initiate.referenceId; context.Entry(item).State = System.Data.Entity.EntityState.Modified; } //Update marked games await context.SaveChangesAsync(); //Create token var token = Utilities.createToken(companyCheck.userName); //Return marked games in our database var gameBankResultVM = await (context.GameBanks.Where(g => g.productCode == initiate.productCode) .Where(l => l.referenceId == initiate.referenceId) .Take(initiate.quantity) .Select(g => new GameBankInitiationResponseVM() { referenceId = g.referenceId, productCode = g.productCode, quantity = initiate.quantity, initiationResultCode = g.initiationResultCode, validatedToken = g.validatedToken, currency = g.currency, estimateUnitPrice = g.estimateUnitPrice, version = g.version, signature = g.signature, ApplicationCode = g.ApplicationCode, companyToken = token, })).ToListAsync(); var resultObj = gameBankResultVM[0]; var initiateResponse = JsonConvert.SerializeObject(resultObj); var responseDB = JsonConvert.DeserializeObject<InitiateResponse>(initiateResponse); //Adding Response into database context.InitiateResponses.Add(responseDB); await context.SaveChangesAsync(); return Ok(resultObj); } #endregion // Add Signature initiate = Utilities.CreateSignature(initiate); #region Insert Request into DB //Adding Request into database context.InitiateRequests.Add(initiate); await context.SaveChangesAsync(); #endregion #region Call Game var httpClient = new HttpClient(); //Convert request var keyValues = initiate.ToKeyValue(); var content = new FormUrlEncodedContent(keyValues); //Call Game Sultan var response = await httpClient.PostAsync("https://test/purchaseinitiation",content); #endregion //Read response var htmlResponse = await response.Content.ReadAsStringAsync(); switch (response.StatusCode) { case HttpStatusCode.NotFound: { return NotFound(); } case HttpStatusCode.InternalServerError: { return InternalServerError(); } case HttpStatusCode.OK: { var initiateResponse = JsonConvert.DeserializeObject<InitiateResponse>(htmlResponse); //Create Token if the result is SUCCESS if (initiateResponse.initiationResultCode == "00") { //Create TOKEN and set initiateResponse.companyToken = Utilities.createToken(companyCheck.userName); } //Adding Response into database context.InitiateResponses.Add(initiateResponse); await context.SaveChangesAsync(); return Ok(initiateResponse); } case HttpStatusCode.BadRequest: { return BadRequest(htmlResponse); } case HttpStatusCode.Unauthorized: { return Unauthorized(); } case HttpStatusCode.RequestTimeout: { return InternalServerError(); } default: { htmlResponse = await response.Content.ReadAsStringAsync(); break; } } return Ok(htmlResponse); } // POST: api/Game //[RequireHttps] For Prod Only [Authorize] [HttpPost, Route("confirmation")] public async Task<IHttpActionResult> PostConfirmation(ConfirmRequest confirm) { if (!ModelState.IsValid) { return BadRequest(ModelState); } #region Validate Company var responseMsg = new HttpResponseMessage(); var isUsernamePasswordValid = false; //Find Company in our database var companyCheck = await (context.Companies.Where(p => p.customerID == confirm.customerID)).SingleOrDefaultAsync(); //Check company password in our database if (companyCheck != null) isUsernamePasswordValid = Utilities.VerifyPassword(confirm.password, companyCheck.hash, companyCheck.salt); //Send UnAuthorized if we don't know the company if (!isUsernamePasswordValid) { // if credentials are not valid send unauthorized status code in response responseMsg.StatusCode = HttpStatusCode.Unauthorized; IHttpActionResult rMessage = ResponseMessage(responseMsg); return rMessage; } #endregion #region Check Code Bank database //Return marked games in our database var gameBankResultVM = await (context.GameBanks .Where(l => l.referenceId == confirm.referenceId) .Select(g => new GameBankConfirmResponseVM() { referenceId = g.referenceId, paymentId = null, productCode = g.productCode, quantity = g.quantity, deliveredQuantity = g.quantity, currency = g.currency, version = g.version, signature = g.signature, ApplicationCode = g.ApplicationCode, productDescription = g.productDescription, unitPrice = g.unitPrice, totalPrice = g.totalPrice, totalPayablePrice = g.totalPrice, coupons = g.coupons.Select(c => new GameCouponBankVM() { Pin = c.Pin, Serial = c.Serial, expiryDate = c.expiryDate }).ToList() })).ToListAsync(); //If we have games in our database, select them! if (gameBankResultVM.Count() != 0) { if (gameBankResultVM.Count <= 1) { //Adding Response into database var responseNew = JsonConvert.SerializeObject(gameBankResultVM[0]); var confirmResponse = JsonConvert.DeserializeObject<ConfirmResponse>(responseNew); context.ConfirmResponses.Add(confirmResponse); await context.SaveChangesAsync(); return Ok(gameBankResultVM[0]); } else if(gameBankResultVM.Count > 1) { var gameResult = new GameBankConfirmResponseVM(); var price = 0.0; var quantity = 0; foreach (var item in gameBankResultVM) { price = price + item.unitPrice; quantity = quantity + 1; foreach (var coupons in item.coupons) { var gameCouponResult = new GameCouponBankVM { expiryDate = coupons.expiryDate, Pin = coupons.Pin, Serial = coupons.Serial }; //Add coupon values gameResult.coupons.Add(gameCouponResult); } } //Set summed/counted values gameResult.referenceId = gameBankResultVM[0].referenceId; gameResult.paymentId = gameBankResultVM[0].paymentId; gameResult.productCode = gameBankResultVM[0].productCode; gameResult.quantity = quantity; gameResult.deliveredQuantity = quantity; gameResult.currency = gameBankResultVM[0].currency; gameResult.unitPrice = gameBankResultVM[0].unitPrice; gameResult.totalPrice = price; gameResult.purchaseStatusDate = gameBankResultVM[0].purchaseStatusDate; gameResult.productDescription = gameBankResultVM[0].productDescription; gameResult.totalPayablePrice = price; gameResult.version = gameBankResultVM[0].version; gameResult.signature = null; gameResult.ApplicationCode = null; //Adding Response into database var responseNew = JsonConvert.SerializeObject(gameResult); var confirmResponse = JsonConvert.DeserializeObject<ConfirmResponse>(responseNew); context.ConfirmResponses.Add(confirmResponse); await context.SaveChangesAsync(); return Ok(gameResult); } } #endregion // Add Signature confirm = Utilities.CreateSignature(confirm); // Save Request into Table context.ConfirmRequests.Add(confirm); await context.SaveChangesAsync(); var httpClient = new HttpClient(); var keyValues = confirm.ToKeyValue(); var content = new FormUrlEncodedContent(keyValues); var response = await httpClient.PostAsync("https://test/purchaseconfirmation", content); var htmlResponse = await response.Content.ReadAsStringAsync(); switch (response.StatusCode) { case HttpStatusCode.NotFound: return NotFound(); case HttpStatusCode.InternalServerError: return InternalServerError(); case HttpStatusCode.OK: //Adding Response into database var newResult = JObject.Parse(htmlResponse); var confirmResponse = JsonConvert.DeserializeObject<ConfirmResponse>(newResult.ToString()); context.ConfirmResponses.Add(confirmResponse); await context.SaveChangesAsync(); return Ok(newResult); case HttpStatusCode.BadRequest: return BadRequest(); case HttpStatusCode.Unauthorized: return Unauthorized(); case HttpStatusCode.RequestTimeout: return InternalServerError(); default: htmlResponse = await response.Content.ReadAsStringAsync(); break; } return Ok(htmlResponse); } [HttpPatch, Route("reconInitiation")] public async Task<IHttpActionResult> PatchReconciliationInitiation(Recon recon) { if (!ModelState.IsValid) { return BadRequest(ModelState); } #region Validate Company var responseMsg = new HttpResponseMessage(); var isUsernamePasswordValid = false; //Company password check var companyCheck = await (context.Companies.Where(p => p.customerID == recon.customerID)).SingleOrDefaultAsync(); if (companyCheck != null) isUsernamePasswordValid = Utilities.VerifyPassword(recon.password, companyCheck.hash, companyCheck.salt); if (!isUsernamePasswordValid) { // if credentials are not valid send unauthorized status code in response responseMsg.StatusCode = HttpStatusCode.Unauthorized; IHttpActionResult rMessage = ResponseMessage(responseMsg); return rMessage; } #endregion //Projection var reconInitiateVm = new ReconInitiationVM(); //Create TOKEN and set reconInitiateVm.companyToken = Utilities.createToken(companyCheck.userName); return Ok(reconInitiateVm); } [Authorize] [HttpPatch, Route("reconConfirmation")] public async Task<IHttpActionResult> PatchReconciliationConfirmation(Recon recon) { if (!ModelState.IsValid) { return BadRequest(ModelState); } #region Validate Company var responseMsg = new HttpResponseMessage(); var isUsernamePasswordValid = false; //Company password check var companyCheck = await (context.Companies.Where(p => p.customerID == recon.customerID)).SingleOrDefaultAsync(); if (companyCheck != null) isUsernamePasswordValid = Utilities.VerifyPassword(recon.password, companyCheck.hash, companyCheck.salt); if (!isUsernamePasswordValid) { // if credentials are not valid send unauthorized status code in response responseMsg.StatusCode = HttpStatusCode.Unauthorized; IHttpActionResult rMessage = ResponseMessage(responseMsg); return rMessage; } #endregion #region Check Our Database DateTime? today = DateTime.Today; var reconResult = await (context.GameBanks.Where(g => g.used == 0) .Where(l => l.referenceId != null) .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.responseDateTime) == today) ).ToListAsync(); if (reconResult.Count() != 0) { foreach (var item in reconResult) { foreach (var reference in recon.referenceId) { if (item.referenceId == reference) { item.used = 1; context.Entry(item).State = System.Data.Entity.EntityState.Modified; } } } await context.SaveChangesAsync(); } #endregion #region Check ConfirmResponse - Razer var reconResultRazer = await (context.ConfirmResponses.Where(g => g.used == 0) .Where(l => l.referenceId != null) .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.purchaseStatusDate) == today) ).ToListAsync(); if (reconResultRazer.Count() != 0) { foreach (var item in reconResultRazer) { foreach (var reference in recon.referenceId) { if (item.referenceId == reference) { item.used = 1; context.Entry(item).State = System.Data.Entity.EntityState.Modified; } } } await context.SaveChangesAsync(); } #endregion return StatusCode(HttpStatusCode.NoContent); } protected override void Dispose(bool disposing) { context.Dispose(); base.Dispose(disposing); } }
Wednesday, May 8, 2019 11:07 AM
All replies
-
User1120430333 posted
I treat the WebAPI controller like I treat the MVC controller where SoC is implemented and the controller doesn't have a whole lot of code in it, a thin controller. It is just my view of it my take on things.
https://en.wikipedia.org/wiki/Separation_of_concerns
https://code-maze.com/aspnetcore-webapi-best-practices/
<copied>
Project Organization
We should always try to split our application into smaller projects. That way we are getting the best project organization and separation of concerns (SoC). The business logic related to our entities, contracts, accessing the database, logging messages or sending an email message should always be in a separate .NET Core Class Library project.<end>
Although the controller I am using is not doing all that the controller you have presented, with your controller I would implement SoC and segregate the logic.
using DAL; using Entities; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; namespace ProgMgmntCore2Api.Controllers { [Produces("application/json")] [Route("api/[controller]")] [ApiController] public class TaskController : ControllerBase, ITaskController { private readonly IDaoTask _daoTask; public TaskController(IDaoTask daoTask) { _daoTask = daoTask; } [HttpGet] [Route("GetTaskById")] public DtoTask GetTaskById(int id) { return _daoTask.GetTaskById(id); } [HttpGet] [Route("GetTasksByProjId")] public List<DtoTask> GetTasksByProjectId(int id) { return _daoTask.GetTasksByProjectId(id); } [HttpPost] [Route("CreateTask")] public void CreateTask(DtoTask dto) { _daoTask.CreateTask(dto); } [HttpPost] [Route("UpdateTask")] public void UpdateTask(DtoTask dto) { _daoTask.UpdateTask(dto); } [HttpPost] [Route("DeleteTask")] public void DeleteTask(DtoId dto) { _daoTask.DeleteTask(dto.Id); } } }
Wednesday, May 8, 2019 12:14 PM -
User-1104215994 posted
I guess I need to deal with duplicate code. How can I make a function of <g class="gr_ gr_57 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar multiReplace" id="57" data-gr-id="57">validate</g> company region in <g class="gr_ gr_73 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar multiReplace" id="73" data-gr-id="73">Utilities</g> class like create signature method?
Wednesday, May 8, 2019 1:22 PM -
User1120430333 posted
I guess I need to deal with duplicate code. How can I make a function of validate company region in Utilities class like create signature method?
You could put the Utilities class in the Models folder, instance the object and use methods/behavior of the Utilities object from the controller
I view the Models folder in WebAPI like I view it in MVC. Again, it's how I view it.
https://www.tutorialsteacher.com/webapi/what-is-web-api
<copied>
ASP.NET Web API
The ASP.NET Web API is an extensible framework for building HTTP based services that can be accessed in different applications on different platforms such as web, windows, mobile etc. It works more or less the same way as ASP.NET MVC web application except that it sends data as a response instead of html view. It is like a webservice or WCF service but the exception is that it only supports HTTP protocol.
<end>
https://www.c-sharpcorner.com/UploadFile/56fb14/understanding-separation-of-concern-and-Asp-Net-mvc/
<copied>
An MVC model contains all of your application logic that is not contained in a view or a controller. The model should contain all of your application business logic, validation logic, and database access logic.
In general, you should strive for fat models and skinny controllers. Your controller methods should contain only a few lines of code. If a controller action gets too fat, then you should consider moving the logic out to a new class in the Models folder.
<end>
https://www.upgrad.com/blog/mvc-architecture-in-java/
<copied>
MVC architectural pattern follows an elementary idea – we must separate the responsibilities in any application on the following basis:
1) Model: Handles data and business logic.
2) View: Presents the data to the user whenever asked for.
3) Controller: Entertains user requests and fetch necessary resources.First, the “pens_controller.php” handles the user request (1) as a GET or POST request. We can also have an “index.php” which is the central controller which will call the “pens_controller” whenever needed.
The controller then examines the request and the parameters and calls the required model – in this case, “pens_model.php”. The controller asks the model to return the list of available pens.
Now, the model searches the database for the necessary information , applies logics if necessary, and returns the data to the controller.
The controller then picks an appropriate view (5) and presents the data (6 and 7). If a request comes from a handheld device, a view suitable for it will be used, or if the user has a particular theme selected, its view will be picked – and so on.
<end>Wednesday, May 8, 2019 3:04 PM