HTTP POST to Many to Many relation using ASP.NET Web API - asp.net

I am new to ASP.net (and programming in general) and I'm having trouble building a Web API. More specifically I need help in these two areas:
How to configure my DOCcontroller to post a new document (DOC table).
How to make the actual ajax post -- I am having trouble passing the EXT_GUID parameter. As it stands I get an error when I try to post. "Can't bind multiple parameters (doc and parentOwner) to the request's content."
Essentially this is for a simple document management system. I want Get/Post documents (DOC) by having the user supply an GUID from an external database (the EXT_GUID field) as a filter/parameter. Each document can have multiple EXT_GUIDs and each EXT_GUID can have multiple Documents (DOC). You can assume that the EXT_GUID fields we be populated prior to the http post.
This is the DOCcontroller code
//POST api/DOC
public HttpResponseMessage PostDOC(DOC doc, List<string> parentOwners)
{
if (ModelState.IsValid)
{
var parents = db.BIMs.Where(bx => parentOwners.Contains(bx.EXT_GUID));
foreach (var p in parents)
doc.Owners.Add(p);
db.DOCs.Add(doc);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, doc);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = doc.Id }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
This is my model setup -- EntityFramework codefirst stuff
public class EXT
{
public int Id { get; set; }
public string EXT_GUID { get; set; }
public int ProjectID { get; set; }
public virtual ICollection<DOC> DOCs { get; set; }
}
public class DOC
{
public int Id { get; set; }
public int ProjectID { get; set; }
public string Subject { get; set; }
public string Link { get; set; }
public virtual ICollection<EXT> EXTs { get; set; }
}
This is more Storage Model...
public StoreDBContext() : base("name=StoreDBContext")
{
}
public DbSet<EXT> EXTs { get; set; }
public DbSet<DOC> DOCs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Set FLUENT API config for many to many here
modelBuilder.Entity<EXT>()
.HasMany(a => a.DOCs)
.WithMany()
.Map(x =>
{
x.MapLeftKey("EXT_Id");
x.MapRightKey("DOC_Id");
x.ToTable("EXTsDOCs");
});
}
AJAX Code
function AddDOC() {
var parentOwner = "{\"" + $('#txtaddEXT').val() + "\"}";
jQuery.support.cors = true;
var DOC = {
ProjectId: ProjectID,
Subject: $('#txtaddDOCSubject').val(),
Link: $('#txtaddDOCLink').val(),
parentOwner: parentOwner
};
$.ajax({
url: "http://localhost:54171/api/DOC/",
type: 'POST',
data: JSON.stringify(DOC),
contentType: "application/json;charset=utf-8",
success: function (data) {
WriteResponse(data);
},
error: function (x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
});
}

What you receive from the client and what you will save in the database is two different things.
Your doc object is ok:
var DOC = {
ProjectId: ProjectID,
Subject: $('#txtaddDOCSubject').val(),
Link: $('#txtaddDOCLink').val(),
parentOwner: parentOwner
};
Now you need to change the server logic. Make a model like this:
public class DocReceivedModel
{
public int ProjectID { get; set; }
public string Subject { get; set; }
public string Link { get; set; }
public List<string> parentOwner { get; set; }
}
Then your PostDOC method will be:
public HttpResponseMessage PostDOC(DocReceivedModel docReceived)
{
if (ModelState.IsValid)
{
Doc newDoc = new Doc();
newDoc.ProjectID = docReceived.ProjectID
newDoc.Subject = docReceived.Subject
newDoc.Link = docReceived.Link
var parents = db.BIMs.Where(bx => docReceived.parentOwners.Contains(bx.EXT_GUID));
foreach (var p in parents)
newDoc.Owners.Add(p);
// I not see in your model Owners, maybe this is EXTs but I suppose you catch the idea
db.DOCs.Add(newDoc);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, newDoc);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new {id = newDoc.Id}));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}

Related

Missing type map configuration exception is showing in my code

I have the following code in the controller and showing exception.
[HttpGet("{id}")]
public IActionResult GetCategoryGoalsById(int id)
{
try
{
var categories = _unitOfWork.Category.GetCategoryByGoalId(id);
if (categories == null)
{
_loggerManager.LogError($"Category with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_loggerManager.LogInfo($"Returned category with id: {id}");
var categoryResult = _mapper.Map<CategoryDetailVm>(categories);
return Ok(categoryResult);
}
}
catch (Exception ex)
{
_loggerManager.LogError($"Something went wrong inside categoryResult action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}
Where is the entity class is like this:
public class Category
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string CategoryName { get; set; }
[ForeignKey(nameof(Goals))]
public int GoalId { get; set; }
public Goals Goals { get; set; }
}
and vm class for the module class written as:
public class CategoryDetailVm
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
The code is written in repository pattern with UnitofWork and the repository part is written as:
public IEnumerable<Category> GetCategoryByGoalId(int goalId)
{
return FindByCondition(g => g.Goals.Id.Equals(goalId)).ToList();
}
following exception is showing here, how can I resolve the following problem:
ex {"Missing type map configuration or unsupported mapping.\r\n\r\nMapping types:\r\nObject ->
CategoryDetailVm\r\nSystem.Object ->
EthosAPI.ViewModelEntities.CategoryDetailVm"} System.Exception
{AutoMapper.AutoMapperMappingException}
It seems like you're missing an automapper mapping, did you add it?
https://docs.automapper.org/en/stable/Getting-started.html#how-do-i-use-automapper
eg. var config = new MapperConfiguration(cfg => cfg.CreateMap<CategoryDetailVm, Categorie>());
Also you're mapping an object to an entire list, so you should also have a mapping for lists, see:
https://docs.automapper.org/en/stable/Lists-and-arrays.html
So var categoryResult = _mapper.Map<CategoryDetailVm>(categories); should be more like var categoryResult = _mapper.Map<IEnumerable<CategoryDetailVm>>(categories); or something.

Redirecting after deleting an item is always returning null

I'm using ASP.NET MVC to build an application for Forums. I have an entity named Posts and an entity named PostReplies.
On a particular Post, there will be a list of replies which are linked by a FK Post_Id in my PostReplies entity.
When I delete a reply on a post and call:
RedirectToAction(GetPost, Post, new { id = post.id});
(gets the individual post, with list of replies on it)
I get an error relating to this bit of code:
var replies = post.Replies;
(the post, always returns null)
I'm not sure why this is, it always redirects fine when I add a reply and then redirect back to the post.
I feel like I'm doing something fundamentally wrong when I'm calling delete method. I'll expand the logic I have below:
Post entity:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime Created { get; set; }
public virtual Discussion Discussion { get; set; }
public virtual ICollection<PostReply> Replies { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
PostReply entity:
public class PostReply
{
public int Id { get; set; }
public string Content { get; set; }
public DateTime Created { get; set; }
public virtual Post Post { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
ReplyController - delete a reply:
[HttpGet]
public ActionResult DeleteReply(int id)
{
return View(_replyService.GetReply(id));
}
[HttpPost]
public ActionResult DeleteReply(int id, PostReply reply, Post posts)
{
var replies = _replyService.GetReply(id);
_replyService.DeleteReply(id, reply, posts);
return RedirectToAction("GetPost", "Post", new { id = posts.Id });
}
_replyService logic (called in the controller above):
public void DeleteReply(int id, PostReply reply, Post posts)
{
var replytoDelete = _context.Replies
.FirstOrDefault(r => r.Id == id);
if (replytoDelete != null)
{
_context.Replies.Remove(replytoDelete);
_context.SaveChanges();
}
}
PostController - get individual post:
public ActionResult GetPost(int id)
{
Post post = _postService.GetPost(id);
var replies = post.Replies;
var listofReplies = replies.Select(reply => new NewPostReplyModel
{
Id = reply.Id,
ReplyPosted = reply.Created,
ReplyContent = reply.Content,
ReplyUserId = reply.ApplicationUser.Id,
ReplyUserName = reply.ApplicationUser.UserName
});
var model = new GetPostViewModel
{
Replies = listofReplies,
Posts = BuildNewPost(post)
};
return View(model);
// return View("GetPost", post);
}
private NewPostModel BuildNewPost(Post post)
{
return new NewPostModel
{
PostId = post.Id,
PostContent = post.Content,
PostTitle = post.Title,
DatePosted = post.Created.ToString(),
DiscussionName = post.Discussion.Title,
DiscussionId = post.Discussion.Id,
UserId = post.ApplicationUser.Id,
UserName = post.ApplicationUser.UserName,
};
}
GetReply logic in service:
public PostReply GetReply(int id)
{
return _context.Replies.Find(id);
}
I think you are not including replies in your GetPost() methode,
So check if your code is like this:
public Post GetPost(int id)
{
return _context.Posts.Include(p=>p.Replies).FirstOrDefault(p=>p.Id == id);
}

How to Assert for Response Code for request using flurl

I was trying to put an assert for Response Code for my request, but i am having hard time to figure out, could you please help me on this. Here is my implementation and definition.
myTests.cs
var accessToken = await helper.SendRequestAsync<AccessToken>(baseUrl, body);
==> how to set assert here right after above statement to verify response status?
helpers.cs
public static async Task<T> SendRequestAsync<T>(string baseUrl, Dictionary<string, string> body)
{
using (var flurl_client = new FlurlClient(baseurl))
{
try
{
var response = await flurl_client
.Request()
.PostUrlEncodedAsync(body)
.ReceiveJson<T>();
return response;
}
catch (Exception ex)
{
Assert.Fail(ex.Message);
}
return default(T);
}
}
======================================================
Data model for "AccessToken" is in Dto.cs
public class AccessToken
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string not_before { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public object Status_Code { get; set; }
}
If you're you looking for Flurl's testing features to help with this, I'm afraid it won't work. Those features are specifically designed for testing the behavior of your client-side code based on fake responses that you set up in your test. It looks like you want to assert the status code from a real call.
The best way I can think of is to drop the .ReceiveJson<T>() line in SendRequestAsync and change the method signature to return Task<HttpResponseMessage>:
using System.Net.Http;
public static async Task<HttpResponseMessage> SendRequestAsync(string baseUrl, Dictionary<string, string> body)
{
using (var flurl_client = new FlurlClient(baseurl))
{
try
{
var response = await flurl_client
.Request()
.PostUrlEncodedAsync(body); // this returns Task<HttpResponseMessage>
return response;
}
catch (Exception ex)
{
Assert.Fail(ex.Message);
}
return null;
}
}
Then your test can do this:
var resp = await Helper.SendRequestAsync(...);
Assert.AreEqual(HttpStatusCode.OK, resp.StatusCode);
Anything that needs the deserialized response body can do this:
var token = await Helper.SendRequestAsync(...).ReceiveJson<AccessToken>();

ASP.NET Cannot get FULL list of ALL countries in the world

There are 196 countries in the world.
I'm trying to show a dropdown list that show all of them.
I see many developer suggest using CultureInfo of ASP.NET but it's missing some countries because Culture & Country are different things.
So how can I get a list of all countries for my purpose please ?. I really appreciate your help.
In ASP.NET a DropDown
<asp:DropDownList ID="selCountries" runat="server"></asp:DropDownList>
is equivalent to
<select id="selCountries"></select>
Alternatively, you could use a Web service to fill a select tag with countries through JavaScript XMLHttpRequest object.
Example: https://restcountries.eu/
Something like this:
(function() {
var newXHR;
function sendXHR(options) { // Helper function.
newXHR = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
if (options.sendJSON === true) {
options.contentType = "application/json; charset=utf-8";
options.data = JSON.stringify(options.data);
} else {
options.contentType = "application/x-www-form-urlencoded";
}
newXHR.open(options.type, options.url, options.async || true);
newXHR.setRequestHeader("Content-Type", options.contentType);
newXHR.send((options.type == "POST") ? options.data : null);
newXHR.onreadystatechange = options.callback;
return newXHR;
}
sendXHR({
type: "GET",
url: "https://restcountries.eu/rest/v1/all",
callback: function() {
if (newXHR.readyState === 4 && newXHR.status === 200) {
var data = JSON.parse(newXHR.response);
var selCountries = document.getElementById("selCountries"); // Get the select tag.
// You can get the selected country.
selCountries.onchange = function() {
alert(this.value);
};
var option;
for (var i = 0; i < data.length; i++) { // For every country make an option tag.
option = document.createElement("option");
selCountries.options.add(option, 0);
selCountries.options[0].value = data[i].name; // Country name from the index «i» of the data array.
selCountries.options[0].innerText = data[i].name;
selCountries.appendChild(option); // Append the option tag to the select tag.
}
}
}
});
})();
<select id="selCountries"></select>
In ASP.NET MVC5 NET 4.5, you can bind an object to #Html.DropDownList by using ViewBag.
You need to create a model according to https://restcountries.eu/rest/v1/all json response.
Model: CountryModel.cs
using System.Collections.Generic;
namespace RestCountries.Models
{
public class Translations
{
public string de { get; set; }
public string es { get; set; }
public string fr { get; set; }
public string ja { get; set; }
public string it { get; set; }
}
public class CountryModel
{
public string name { get; set; }
public string capital { get; set; }
public List<string> altSpellings { get; set; }
public string relevance { get; set; }
public string region { get; set; }
public string subregion { get; set; }
public Translations translations { get; set; }
public int population { get; set; }
public List<object> latlng { get; set; }
public string demonym { get; set; }
public double? area { get; set; }
public double? gini { get; set; }
public List<string> timezones { get; set; }
public List<object> borders { get; set; }
public string nativeName { get; set; }
public List<string> callingCodes { get; set; }
public List<string> topLevelDomain { get; set; }
public string alpha2Code { get; set; }
public string alpha3Code { get; set; }
public List<string> currencies { get; set; }
public List<object> languages { get; set; }
}
}
Controller: DefaultController.cs
using RestCountries.Models;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web.Mvc;
namespace RestCountries.Controllers
{
public class DefaultController : Controller
{
// GET: Default
public ActionResult Index()
{
string url = "https://restcountries.eu/rest/v1/all";
// Web Request with the given url.
WebRequest request = WebRequest.Create(url);
request.Credentials = CredentialCache.DefaultCredentials;
WebResponse response = request.GetResponse();
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string jsonResponse = null;
// Store the json response into jsonResponse variable.
jsonResponse = reader.ReadLine();
if (jsonResponse != null)
{
// Deserialize the jsonRespose object to the CountryModel. You're getting a JSON array [].
List<CountryModel> countryModel = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CountryModel>>(jsonResponse);
// Set the List Item with the countries.
IEnumerable<SelectListItem> countries = countryModel.Select(x => new SelectListItem() { Value = x.name, Text = x.name });
// Create a ViewBag property with the final content.
ViewBag.Countries = countries;
}
return View();
}
}
}
View: Index.cshtml
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DropDownList("Countries")
Result:

Web API, EF Code First and Duplicate Records

I am creating a REST service using Web API and am using EF Code First to retrieve and store data to back end that service. I have a bunch of classes defined and the EF creates the database no problems at all. I can retrieve the data and again have no issues. The problem I experience is when I try to write back a record. If I have created the following little mock up to reproduce the problem.
public class Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
}
public class Contact
{
[Key]
public int ContactID { get; set; }
public int CompanyID { get; set; }
public virtual Company Company { get; set; }
public string Name { get; set; }
}
public class Quote
{
[Key]
public int QuoteID { get; set; }
public int CustomerID { get; set; }
[ForeignKey("CustomerID")]
public virtual Company Customer { get; set; }
public int ContactID { get; set; }
public virtual Contact Contact { get; set; }
public string Reference { get; set; }
}
I then have three controllers in my REST service;
public class CompaniesController : ApiController
{
TestDBContext Context = new TestDBContext();
public IEnumerable<Company> GetCompanies()
{
return Context.Companies;
}
}
[Route("api/companies/{CompanyID}/contacts")]
public class CompanyContactsController : ApiController
{
TestDBContext Context = new TestDBContext();
public IEnumerable<Contact> GetCompanyContacts(int CompanyID)
{
return Context.Contacts.Where(C => C.CompanyID == CompanyID);
}
}
public class QuotesController : ApiController
{
TestDBContext Context = new TestDBContext();
public HttpResponseMessage PostQuote(Quote value)
{
try
{
if (ModelState.IsValid)
{
Context.Quotes.Add(value);
Context.SaveChanges();
// Return the object in the body.
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, value);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = value.QuoteID }));
return response;
}
}
catch
{
}
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
I pre-populate the database with a company and a contact and then have the following code to create a quote.
HttpClient Client = new HttpClient();
string CompanyURL = "http://localhost:56619/api/Companies";
string ContactURL = "http://localhost:56619/api/Companies/{0}/Contacts";
string QuoteURL = "http://localhost:56619/api/Quotes";
Client.GetAsync(CompanyURL).ContinueWith((T) =>
{
T.Result.EnsureSuccessStatusCode();
T.Result.Content.ReadAsAsync<IEnumerable<Company>>().ContinueWith((T2) =>
{
Company Comp = T2.Result.First();
Client.GetAsync(string.Format(ContactURL, Comp.CompanyID)).ContinueWith((T3) =>
{
T3.Result.EnsureSuccessStatusCode();
T3.Result.Content.ReadAsAsync<IEnumerable<Contact>>().ContinueWith((T4) =>
{
Contact Cont = T4.Result.First();
Quote Q = new Quote() { Customer = Comp, Contact = Cont, Reference = "Test" };
JsonMediaTypeFormatter Formatter = new JsonMediaTypeFormatter();
Formatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Client.PostAsync<Quote>(QuoteURL, Q, Formatter).ContinueWith((T5) =>
{
T5.Result.EnsureSuccessStatusCode();
T5.Result.Content.ReadAsAsync<Quote>().ContinueWith((T6) =>
{
var Res = T6.Result;
});
});
});
});
});
});
I know the code is a little scrappy, but it is just thrown together to illustrate the problem without any extra stuff cluttering the code up.
When the EF saves the changes to the database it creates 1 quote, an extra contact and 2 extra company records. I'm puzzled as to why. I am using VS2013, EF6 and Web API 2.
If anyone has any suggestions they would be very gratefully be received.
Many thanks in advance,
Neil.

Resources