I'm new in Angular, and I'm currently into a big project where many people work, so the project is based on Angular for the front end and C# for the back end.
So, the project has some services that call the backend service:
Front:
public editProfileClient(profileClient) {
return this.http
.post(this.url + '/editProfileClient', profileClient)
.pipe(map((result: ProfileClientModel) => result));
}
Back:
public async Task<ActionResult> EditProfileClient(ProfileClient profileClient)
{
//irrelevant code here
return Ok(model);
}
This is working well, but now I want to send a new model called Salary to that request, so I changed the back as:
public async Task<ActionResult> EditProfileClient(ProfileClient profileClient, Salary salary)
but I have no idea how I can send it on the front, so I receive it, but I cannot call it:
public editProfileClient(profileClient, salary) {
return this.http
.post(this.url + '/editProfileClient', profileClient, salary)
.pipe(map((result: ProfileClientModel) => result));
}
If I try to do that, the method returns an error:
Argument of type 'OperatorFunction<ProfileClientModel,
ProfileClientModel>' is not assignable to parameter of type
'OperatorFunction<ArrayBuffer, ProfileClientModel>'.
How can I achieve that?
For the API part, combine both parameters into a single object as below:
public async Task<ActionResult> EditProfileClient(EditProfileClientInputModel input)
public class EditProfileClientInputModel
{
public ProfileClient ProfileClient { get; set; }
public Salary Salary { get; set; }
}
For the front-end part:
2.1. Combine both profileClient and salary parameters into a single object and pass it.
2.2. As your API returns the response of ProfileClientModel type, you should also specify the generic type: post<T>()
public editProfileClient(profileClient, salary) {
let input = {
profileClient: profileClient,
salary: salary
};
return this.http
.post<ProfileClientModel>(this.url + '/editProfileClient', input);
}
Update
.pipe(map((result: ProfileClientModel) => result))
As per Eliseo's feedback, the pipe() should be removed as map() is used to transform the data, while you try to transform the value into the same value, which is unnecessary.
Related
Team.
I am working on creating dynamic API for my application my API looks like below,
[HttpPost]
[Route("publish")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Publish(PublishEventDto publishEventDto)
{
await _eventDomain.PublishEventAsync(publishEventDto);
return Ok();
}
and dto structure will be
public class PublishEventDto
{
public string EventName { get; set; }
public dynamic Payload { get; set; }
}
now when I am trying to call my API it is showing double brace in input param I am unable to create object from it.
You could use an ExpandoObject or the object type as an alternative to dynamic.
It's work as a Dictionary
I have using RestSharp to test APIs and I have a delete call that I want to run twice in the same method.
The delete call will delete with two different query params. It only can take one query param at a time so I want to call it twice with the two different query params. What is the best optimized solution to this.
The example below I am deleting with the user id 1 and I want to also delete with user id 2
[Test]
public void DeletedUser()
{
response = HttpDelete("url");
QueryParam.Add("id", 1);
Assert.statusCode(200, response);
}
I have used Andy solution to use TestCase attribute but I get an syntax error when trying to not hard code the data being used.
Error Message: "An attribute argument must be a constant expression , typeof expression or array creation expression of an attribute parameter type"
ex..
public static string data = "1"
[TestCase(data)] //Getting the error here
[Test]
public void DeletedUser(string id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
I need to run the call using two dynamic test data. The data gets generated from a Post call before the Delete call and it gets saved and serialized into a class where I have the data variables..
Here is an example of the class where the test data is stored
public class Data
{
public class UserData
{
public string id1;
public string id2;
public UserData()
{
id1 = "";
id2 = "";
}
}
}
This is the Post call and how the data is being saved.
[Test]
public void AddUser()
{
response = HttpPost("url", modle);
Data data = new Data()
data.UserData.id1 = response.content;
}
How can I now use this data.UserData.id1 in my TestCase attribute
You can make use of NUnit's [TestCase] attribute to run a test multiple times with different parameters.
Example
[TestCase(1)]
[TestCase(2)]
public void DeletedUser(int id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
You can extend this with as many parameters as needed to complete a test. For example, if you expect different responses for different IDs:
[TestCase(1, 200)]
[TestCase(2, 404)]
public void DeletedUser(int id, int expectedResponseCode)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(expectedResponseCode, response);
}
Full documentation is available here.
Update
In response to your further question about testing with dynamic data, as Charlie said you can only reference literals or literal constants from an attribute so you won't be able to use [TestCase] with dynamic data.
Instead you could use the [TestCaseSource] attribute. Create a static method that retrieves your test data and returns an array, and then quote the name of this method in the attribute.
private static int[] GetIdsToDelete() {
// UserData userData = ... read in data
return new int[] {
userData.id1,
userData.id2
}
}
[TestCaseSource(nameof(GetIdsToDelete))]
public void DeletedUser(int id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
You can find the full documentation for TestCaseSource here.
Currently I'm trying to create a web api based on asp.net core 2.0 and I'd like to create a nested route. In case of a put request it sends a part of the information in the route and another part in the body.
Requirements
The desired url to call would be
https://localhost/api/v1/master/42/details
If we'd like to create a new detail below our master 42 I would expect to send the data of the details in the body while the id of the master comes out of the route.
curl -X POST --header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{ \
"name": "My detail name", \
"description": "Just some kind of information" \
}' 'https://localhost/api/v1/master/42/details'
The outcoming response of the request would be
{
"name": "My detail name",
"description": "Just some kind of information",
"masterId": 42,
"id": 47
}
and a location url within the response header like
{
"location": "https://localhost/api/v1/master/42/details/47
}
Work done so far
To get this to work I created this controller:
[Produces("application/json")]
[Route("api/v1/master/{masterId:int}/details")]
public class MasterController : Controller
{
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var response = await Do.Process(request);
return CreatedAtAction(nameof(Get), new { id = response.Id }, response);
}
}
Which uses these classes:
public class DetailCreateRequest
{
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DetailResponse
{
public int Id { get; set; }
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The problem
So far most of the stuff works as expected. The only thing that really doesn't work is merging the MasterId from the route into the DetailCreateRequest that comes from the body.
First try: Use two attributes on the parameter
I tried to combine these two things by this action call:
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
But the incoming object only had a MasterId of zero. If I change the order of the two attributes, then only the id from the route will be taken and all values within the body are ignored (so seems to be first attribute wins).
Second try: Use two different parameters in action
Another approach that I tried was this action call:
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request)
In the first spot this looks okay, cause now I have both values within the controller action. But my big problem with this approach is the model validation. As you can see in the above code I check ModelState.IsValid which was filled through some checks from FluentValidation, but these checks can't be really done, cause the object wasn't build up correctly due to the missing master id.
(Not-working) Idea: Create own attribute with merge parameters
Tried to implement something like this:
public async Task<IActionResult> Post([FromMultiple(Merge.FromBody, Merge.FromRoute)]DetailCreateRequest request)
If we already would have something like this, that would be great. The order of the arguments within the attribute would give out the order in which the merge (and possible overwrites) would happen.
I already started with implementing this attribute and creating the skeleton for the needed IValueProvider and IValueProviderFactory. But it seems to be a quite lot of work. Especially finding all the nifty details to make this work seamlessly with the whole pipeline of asp.net core and other libraries I'm using (like swagger through swashbuckle).
So my question would be, if there already exists some mechanism within asp.net core to achieve such a merge or if anybody is aware about an already existing solution or about a good example on how to implement such a beast.
Solution so far: Custom ModelBinder
After getting the answer from Merchezatter I look into how to create a custom model binder and came up with this implementation:
public class MergeBodyAndValueProviderBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var body = bindingContext.HttpContext.Request.Body;
var type = bindingContext.ModelMetadata.ModelType;
var instance = TryCreateInstanceFromBody(body, type, out bool instanceChanged);
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var setters = type.GetProperties(bindingFlags).Where(property => property.CanWrite);
foreach (var setter in setters)
{
var result = bindingContext.ValueProvider.GetValue(setter.Name);
if (result != ValueProviderResult.None)
{
try
{
var value = Convert.ChangeType(result.FirstValue, setter.PropertyType);
setter.SetMethod.Invoke(instance, new[] { value });
instanceChanged = true;
}
catch
{ }
}
}
if (instanceChanged)
bindingContext.Result = ModelBindingResult.Success(instance);
return Task.CompletedTask;
}
private static object TryCreateInstanceFromBody(Stream body, Type type, out bool instanceChanged)
{
try
{
using (var reader = new StreamReader(body, Encoding.UTF8, false, 1024, true))
{
var data = reader.ReadToEnd();
var instance = JsonConvert.DeserializeObject(data, type);
instanceChanged = true;
return instance;
}
}
catch
{
instanceChanged = false;
return Activator.CreateInstance(type);
}
}
}
It tries to deserialize the body into the desired object type and afterwards tries to apply further values from the available value providers. To get this model binder to work I had to decorate the destination class with the ModelBinderAttribute and made the MasterId internal, so that swagger doesn't announce it and JsonConvert doesn't deserialize it:
[ModelBinder(BinderType = typeof(MergeBodyAndValueProviderBinder))]
public class DetailCreateRequest
{
internal int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Within my controller the action method parameters are still containing the [FromBody] flag, cause it is used by swagger to announce on how the method can be called, but it never will be called, cause my model binder has a higher priority.
public async Task<IActionResult> Post([FromBody]DetailCreateRequest request)
So it is not perfect, but works good so far.
That is looks like a right choice:
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request) {
//...
}
But if you have some problems with domain model validation, create custom Dto object without master Id.
Otherwise you can use custom model binder, and then work with arguments from action and binding contexts.
I'm not sure if this works in Asp.Net-Core 2.0, but we use the following in 3.1 to have a single request object which gets its properties from multiple locations:
// Annotate the action parameter with all relevant attributes
public async Task<IActionResult> Post([FromBody][FromRoute][FromQuery]DetailCreateRequest request) { ... }
// Annotate each property separately, so the binder(s) don't overwrite
public class DetailCreateRequest
{
[FromRoute]
public int MasterId { get; set; }
[FromBody]
public string Name { get; set; }
[FromQuery]
public string Description { get; set; }
}
It works with .Net 6:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<CustomerResponse>> Post([FromRoute, FromBody] CustomerPostRequest request)
{
return Ok();
}
public class CustomerPostRequest
{
[FromRoute(Name = "id")]
public int Id { get; set; }
[FromBody]
public string Name { get; set; }
}
Set the your required "source" attributes on the single request object parameter, and inside this object add each property the relevant "source" attribute.
Make sure the FromBody is the last one (it didn't work when I switched them).
I have Web API service deployed and and consuming in another web application. Web API method take complex object (List object) and results also complex object.
So I created local models for Input parameter and results model to match with Web API complex objects in web application. then I passed JsonConvert.SerializeObject for that parameter. But when I debug in Web API that parameter value showing null.
Web application
[Serializable]
public class PreferencesInput
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
.....
}
[Serializable]
public class PreferencesOuput
{
public bool Status { get; set; }
public string Error { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
RunAsync().Wait();
return View();
}
private static async Task RunAsync()
{
var inputs = new List<PreferencesInput>();
var input = new PreferencesInput
{
ShortName = "REGION",
ShortNameDescription = "Geographical regions",
OptedInFlag = true
};
inputs.Add(input);
....
...
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:8585/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = await client.GetAsync("preferences/updatepreferences/?id='3016523'
&optInInterestAreas=" + JsonConvert.SerializeObject(inputs) +
"&solicitationFlag=false").ConfigureAwait(false);;
if (response.IsSuccessStatusCode)
{
string results = await response.Content.ReadAsStringAsync();
var myList = JsonConvert.DeserializeObject<List<PreferencesOuput>>(results);
}
web API
[Route("preferences/updatepreferences")]
[HttpGet]
public PreferencesOuput UpdatePreferences(string id, IEnumerable<PreferencesInput> optInInterestAreas, bool solicitationFlag)
{
.....
}
Only difference is Web application Input model has less parameters than the Web API model.
What I am doing wrong here?
IEnumerable<PreferencesInput> optInInterestAreas is null
update
I can see serialization date like below before sending to Web API call, In Web API method it is showing null, rest of the parameters are showing correct.
[{"ShortName":"REGION","ShortNameDescription":"Geographical regions","ShortSubName":null,"Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Asia Pacific","ShortSubName":"ASIA_PACIFIC","Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Canada","ShortSubName":"CANADA","Description":null,"OptedInFlag":true}]
You could try to specify the route with parameters. Something like:
[Route("preferences/updatepreferences/{id}/{optInInterestAreas}/{solicitationFlag:bool}")]
Your optInInterestAreas parameter is null because in Web API, the parameter binding rules specify that anything other than a "simple" parameter type (string, int, etc) is assumed to be passed in the body, not the route or query string as you're doing. You could get this to work by using the [FromUri] attribute on that parameter or by defining a custom type converter, but I would highly recommend changing your API as it does not follow generally accepted best practices.
By convention, GET is assumed to be side-effect-free, but I'm guessing something called UpdatePreferences almost certainly changes data. I would consider using a different verb and passing the updated preferences in the body. POST is better, but if you want it to be truly RESTful, you should ensure that the URI uniquely identifies the resource and use PUT.
I would start by changing your input model to something like this:
public class PreferencesInput
{
public IList<InterestArea> InterestAreas { get; set; }
public bool SolicitationFlag { get; set; }
}
public class InterestArea
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
...
}
Then define your API action like this:
[Route("preferences/{id}")]
[HttpPut]
public PreferencesOuput UpdatePreferences(string id, PreferencesInput preferences)
{
...
}
As you can see, the URI now uniquely identifies the thing, and the verb specifies what you want to "do"; in this case, completely replace whatever is at that URI (if anything) with the thing you are passing.
Side-note:
On the MVC side, calling Wait() in your Index action is blocking a thread while waiting for your async method to complete. That's a serious invitation for deadlocks. Async only works properly if you go "all the way" with it. In this case it's incredibly easy - just change the Index action to:
public async Task<ActionResult> Index()
{
await RunAsync();
return View();
}
I'm developing a web service, using WEB .API. I'm following the example, which include:
public HttpResponseMessage PostProduct(Product item)
{
item = repository.Add(item);
var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
string uri = Url.Link("DefaultApi", new { id = item.Id });
response.Headers.Location = new Uri(uri);
return response;
}
for creating a POST method, allowing a client to send data in POST in ordert to insert these data in the database (I'm using Entity Framework).
What I want do, however, is slightly difference, since the data I want pass in post to the web service are not associated to any object of database: I have some data that should be write in more then one table. For example:
{"activity":"sport","customValue":"22","propertyIndex":"122-x"}
The activty value (sport) should be writed on one table, while the others two parameters (customValue e properyIndex) shouldbe writed on another table.
So I think I need to parse the json file received in POST and then execute the two insert operation.
How can I perform this task?
You need to create an object in web API project with Activity, CustomValue, PropertyIndex properties:
public class MyTestClass
{
public string Activity { get; set; }
public string CustomValue { get; set; }
public string PropertyIndex { get; set; }
}
and HttpPost will be:
[HttpPost]
public HttpResponseMessage Post(MyTestClass class)
{
// Save Code will be here
return new HttpResponseMessage(HttpStatusCode.OK);
}
Product class should have Activity, CustomValue and PropertyIndex properties to get bind with posted data.
[HttpPost]
[ActionName("alias_for_action")]
public HttpResponseMessage PostProduct([FromBody] Product item)
{
//your code here
var response = new HttpResponseMessage(HttpStatusCode.Created)
{
Content = new StringContent("Your Result")
};
return response;
}
Yes if you want to update two tables in database using Entity Framework then you have to execute two insert operations.