Angular JS - send data to .NET Controller API - asp.net

I have this angular js code here:
$http.post('/reports/', JSON.stringify($scope.user));
and its hitting my Reports Controller Post method:
[HttpPost]
public dynamic Post(Array data){
//do something
}
but when I check the data in my Post method when it hits in my breakpoint it appears as null :( how do I pass the data from $scope.user to my Controller. I did a console.log of $scope.user and the data is there, it is an object but trying to pass it in as JSON.
I found this:
public HttpResponseMessage Post([FromBody]Customer cust)
{
var newCust = _Repository.InsertCustomer(cust);
if (newCust != null)
{
var msg = new HttpResponseMessage(HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + newCust.ID.ToString());
return msg;
}
throw new HttpResponseException(HttpStatusCode.Conflict);
}
would I have to put [FromBody] Reports report instead of Array data

Just do this simple as possible, you are missing the parameter name:
$http.post('/reports/', {data: $scope.user});
Make sure that $scope.user is an Array, else change the type.

Related

How can I use a default value/model on WebAPI EmptyBody?

I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]

Consuming Asp.Net Core api locally

I don't know if my google skills are diminishing or what but I can't seem to figure out how to consume a local api. This may be best explained with sample code...
So I have a simple api
public class FooApiController : Controller
{
public IActionResult GetFoo(int id)
{
if (id == 0)
return BadRequest();
var data = ... do db access
return Ok(data);
}
}
and a view controller
public class FooController : Controller
{
public IActionResult Foo()
{
var api = new FooApiController();
var data = api.GetFoo(1);
ViewBag.Data = data;
return View();
}
}
So in the above view controller I call the api to get the data needed. However, being that the api controller returns an IActionResult, ViewBad.Data ends up being an IActionResult object. So how do I change the above to check the StatusCode of the api call, handle errors if need be, and if not... put just the data into the ViewBag, instead of the entire result object.
Every sample I have found seems to have the view controller return a view that then uses an ajax call to get the data. While I understand and could easily do that, I don't like the idea of making 2 round trips to the server when I don't need to.
You are doing it wrong.
If you want to reuse the code among multiple controllers, then it is better to move it from the GetFoo method and put it into a shared class and access it from everywhere else.
If you want to call it from a view through REST, then call it using $.ajax
ex:
$.ajax('FooApi/GetFoo/5',function(data){alert(data);});
If you want to access it from another C# client, then use the HttpClient class, ex:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/FooApi/GetFoo", 3);
response.EnsureSuccessStatusCode();

How to send List of objects to google execution api and handle it in google apps script

In general I want to export data from asp.net mvc application to Google Sheets for example list of people. I've already set up connection and authenticated app with my Google account (trough OAuth2) but now I'm trying to send my list of objects to api and then handle it in script (by putting all data in new file) and couldn't get my head around this.
Here is some sample code in my app that sends the request.
public async Task<ActionResult> SendTestData()
{
var result = new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(CancellationToken.None).Result;
if (result.Credential != null)
{
string scriptId = "MY_SCRIPT_ID";
var service = new ScriptService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "Test"
});
IList<object> parameters = new List<object>();
var people= new List<Person>(); // next i'm selecting data from db.Person to this variable
parameters.Add(people);
ExecutionRequest req = new ExecutionRequest();
req.Function = "testFunction";
req.Parameters = parameters;
ScriptsResource.RunRequest runReq = service.Scripts.Run(req, scriptId);
try
{
Operation op = runReq.Execute();
if (op.Error != null)
{
// The API executed, but the script returned an error.
// Extract the first (and only) set of error details
// as a IDictionary. The values of this dictionary are
// the script's 'errorMessage' and 'errorType', and an
// array of stack trace elements. Casting the array as
// a JSON JArray allows the trace elements to be accessed
// directly.
IDictionary<string, object> error = op.Error.Details[0];
if (error["scriptStackTraceElements"] != null)
{
// There may not be a stacktrace if the script didn't
// start executing.
Newtonsoft.Json.Linq.JArray st =
(Newtonsoft.Json.Linq.JArray)error["scriptStackTraceElements"];
}
}
else
{
// The result provided by the API needs to be cast into
// the correct type, based upon what types the Apps
// Script function returns. Here, the function returns
// an Apps Script Object with String keys and values.
// It is most convenient to cast the return value as a JSON
// JObject (folderSet).
Newtonsoft.Json.Linq.JObject folderSet =
(Newtonsoft.Json.Linq.JObject)op.Response["result"];
}
}
catch (Google.GoogleApiException e)
{
// The API encountered a problem before the script
// started executing.
AddAlert(Severity.error, e.Message);
}
return RedirectToAction("Index", "Controller");
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
The next is how to handle this data in scripts - are they serialized to JSON there?
The execution API calls are essentially REST calls so the payload should be serialized as per that. Stringified JSON is typically fine. Your GAS function should then parse that payload to consume the encoded lists
var data = JSON.parse(payload);

How to handle a POST request to a URL that contains route parameters?

I'm working on an ASP.NET MVC4 web app, and I have a controller method for handling a GET request with an id in the URL, like so ...
[PortalAuthorization]
public ActionResult View(int id)
{
// get the individual ftp log
PortalFTPLog log = PortalFTPLogs.Get(id);
if (log == null)
{
TempData["Error"] = "The provided ftp log id does not exist.";
return RedirectToAction("Index");
}
// get the available matters to tie uploads to
ViewBag.matters = PortalMatters.Get();
return View(log);
}
In my view for this controller method, I have a form so that they can update it, that I want to POST back to the same URL. A URL like foo.com\items\1. Thats what the function above handles.
How do I make a function that handles a POST request for a function that requires a parameter, though? IN previous POST handlers I create a FormsCollection param, but when I add it to the param list for this function, the id param is null.
[HttpPost]
[PortalAuthorization]
public ActionResult View(FormCollection collection, int id)
{
PortalFTPLog log = PortalFTPLogs.Get(id);
if (log == null)
{
TempData["Error"] = "The provided ftp log id does not exist.";
return RedirectToAction("Index");
}
// update the matter id and save to database
log.Matter = Convert.ToInt32(collection.Get("matter"));
log.Save();
TempData["Notice"] = "The FTP log meta data has been updated.";
return RedirectToAction("View", new { id = id });
}
You need to provide RouteValues in Html.BeginForm on your View:
#using (Html.BeginForm(new {id = someIntIdValue}))
{
// Your form code
}

Validating a field based on a different database table / entity

I am writing an MVC 4 application, and using Entity Framework 4.1. I have a validation question which I cannot seem to find the answer to.
Essentially, I have an Entity (object) called "Product" which contains a field "Name", which must follow strict naming conventions which are defined in a separate Entity called "NamingConvention". When the user enters a value, the system needs to check it against the rules established in the NamingConvention entity, and return an error if need be.
Where should this validation be done, and how? I need to check the NamingConvention entity when doing the validation, which means I would need a database context since I'm referencing a different entity. Is there any validation method which won't require me to create a new context? I was thinking of doing the validation in the Controller, since it already creates a data context, but this doesn't seem like the right place to do it.
Thanks for any help!
I have done things like this using a JQuery post (ajax) call from the webpage where the name is being entered. You then post (the value of name) to a method on your controller which can return a JSON value that contains a flag saying if the validation passed and also a message that you want to return to your user. For example :
Javascript in webpage :
$("#name").change(function () {
var nameVal = $(this).val();
$.post(getRoot() + "/NameController/ValidateName", { name: nameVal },
function (data) {
if (data.valid == "true") {
alert("A valid name was chosen");
} else
{
alert(data.message);
}
}, "json");
});
Controller (NameController) Code :
[HttpPost]
public ActionResult ValidateName(string name)
{
// actual validation carried out in a static utility class (Utils.IsNameValid)
// if you are loading the same validation rules from your table each time
// consider caching the data in the application cache or a static List.
bool nameIsValid = Utils.IsNameValid(name, out string ErrorMessage);
JsonResult result = new JsonResult();
result.Data = new { valid = (nameIsValid "true" : "false"), message = ErrorMessage };
return result;
}
I'm using EF 5 but believe you can use this method ... apologies in advance if I'm misleading you with this answer.
You could do the validation within your context (or a context decorator)
public override int SaveChanges()
{
var products = this.GetChangedProducts();
foreach (var product in products)
{
this.ValidateName(product);
}
return base.SaveChanges();
}
private IEnumerable<Product> GetChangedProducts()
{
return (
from entry in _context.ChangeTracker.Entries()
where entry.State != EntityState.Unchanged
select entry.Entity)
.OfType<Product>();
}
private void ValidateName(Product product)
{
//validate here
}

Resources