How can I use a default value/model on WebAPI EmptyBody? - asp.net

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]

Related

ASP.NET Core 2 IFormCollection returning StringValues

Recently the IFormCollection in the platform I'm building started returning values of the type Microsoft.Extensions.Primitives.StringValues. when it used to return strings.
The controllers were made with strings in mind and now that are a lot of forms that are not working.
Is there any explanation to this, or a way to revert it?
As far as I'm aware ASP.NET Core's IFormCollection has always been a collection of StringValues. The reason is simple: multiple values can be posted for any particular key, making it potentially impossible to set the value if the type was merely string. There is no way to "revert" this. Change your code accordingly.
Or, better yet, stop using IFormCollection. Bind to strongly-typed models. That's always the best way.
For others coming here also confused by seeing IFormCollection giving StringValues. You may be familiar with .NET Frameworks FormCollection class, which gives strings. The reason for the change is valid and explained by #Chris Pratt in his answer here.
To make IFormCollection and StringValues feel familiar again consider any of these simple extensions:
// Example: var name = collection["name"].FirstOrNull();
// Equal to (.NET Framework): var name = collection["name"];
public static string FirstOrNull(this StringValues values)
{
if (values.Count > 0)
{
return values[0];
}
return null;
}
// Example: var name = collection["name"].FirstOr("John Doe");
// Equal to (.NET Framework): var name = collection["name"] ?? "John Doe";
public static string FirstOr(this StringValues values, string fallback)
{
if (values.Count > 0)
{
return values[0];
}
return fallback;
}
// Example: var name = collection.ValueOrFallback("name", "John Doe");
// Equal to (.NET Framework): var name = collection["name"] ?? "John Doe";
public static string ValueOrFallback(this IFormCollection collection, string key, string fallback)
{
if (collection[key].Count > 0)
{
return collection[key][0];
}
return fallback;
}
Also consider the built-in TryGetValue:
if (collection.TryGetValue("name", out var name))
{
// at least one name did exist
}
Alt.
var name = collection.TryGetValue("name", out var names) ? names[0] : "John Doe";

Setting a default page size for a WebAPI 2 OData service

I have an ODataController with an endpoint like this:
[EnableQuery]
public IQueryable<Customer> Get()
{
return _context.Customers;
}
And this setting in the Register method of the WebApiConfig:
config.Count().Filter().OrderBy().Expand().Select().MaxTop(100);
There are a lot of Customer entries and I don't want the client requesting too many at once as the query will take a very long time. Luckily this setting means if they do a request like this:
http://domain.com/api/Customers?$top=1000
It'll prevent them from retrieving them as it's higher than 100.
However, if they do a request like this:
http://domain.com/api/Customers
This then attempts to retrieve all customers, which I don't want.
I know I can set the page size like this:
[EnableQuery(PageSize = 10)]
public IQueryable<Customer> Get()
{
return _context.Customers;
}
And this will only return 10 results, however I still want the user to be able to specify their own $top and $skip values for paging (and deciding how many results they want per page). What I want is for there to be a maximum of 100 and a default of 10.
How can I achieve this?
EDIT
I tried the following but it doesn't work properly when using Expand clauses. Any other ideas?
[EnableQuery(MaxTop = 100)]
public IQueryable<Customer> Get(ODataQueryOptions<Customer> queryOptions)
{
IQueryable<Customer> query = _context.Customers;
int? top = queryOptions?.Top?.Value;
if (top == null)
{
query = query.Take(10);
}
return query;
}
Define class:
public class EnableQueryWithDefaultPageSizeAttribute : EnableQueryAttribute
{
const int pageSizeDefault = 10;
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
int? top = queryOptions?.Top?.Value;
if (top == null)
{
return queryOptions.ApplyTo(queryable, new ODataQuerySettings { PageSize = this.PageSize == 0 ? pageSizeDefault : this.PageSize });
}
return queryOptions.ApplyTo(queryable); ;
}
}
and use it instance of EnableQuery. See this.
I was facing the same thing, but I was able to get it working. It looks like the order of parameters matters. You'll have to forgive me, I am very new at this and learning as I go, so I hope this actually helps.
My API controller looks like this:
[EnableQuery(PageSize=100)]
public IQueryable<APPLICATION_LOGS> Get()
{
return db.APPLICATION_LOGS;
}
In my client, I am building a request and capturing the response:
string entity = "/AuditApplicationLog";
string expandOptions = "&$expand=APPLICATION, APPLICATIONS_SUBSYSTEMS, FILE";
string parameters = "?$top=100&$count=true";
string url = serviceUri + entity + parameters + expandOptions;
WebRequest request = WebRequest.Create(url);
WebResponse response = request.GetResponse();
This is all a practice project that I am just using to teach myself a few concepts.The page size in my API controller is set to 100 but you'll notice in the parameters variable I am setting a top value. If I set this value to 25, it will return 25. If I set it to 50, it will return 50 etc. Apparently the order I set the parameters matters. If I set the top value before the expand parameters, it works. If I attempt to set it after the expand parameters it returns the default 100, regardless of the value I set top at. It does appear to leave out the "nextPage" link, but that can be accounted for. I hope this helps.

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
}

Alfresco: Custom Share Evaluator based on some custom repo webscripts

So I'd like to write a new set of evaluators in Share based on the result of some repository webscripts.
The current existing Share evaluators are usable through some XML configuration and are related to Alfresco usual meta-data.
But, I'd like to know how to write my own Java evaluator while re using most of the logic already here (BaseEvaluator).
Suppose I have a repository webscript that returns some JSON like {"result" : "true"}:
How do I access it from my custom Evaluator? Mainly how do I access the proxy URL to alfresco webapp from the Spring context?
Do I need to write my own async call in Java?
Where do I find this JSONObject parameter of the required evaluate method?
thanks for your help
See if this helps. This goes into a class that extends BaseEvaluator. Wire the bean in through Spring, then set the evaluator on your actions.
public boolean evaluate(JSONObject jsonObject) {
boolean result = false;
final RequestContext rc = ThreadLocalRequestContext.getRequestContext();
final String userId = rc.getUserId();
try {
final Connector conn = rc.getServiceRegistry().getConnectorService().getConnector("alfresco", userId, ServletUtil.getSession());
final Response response = conn.call("/someco/example?echo=false");
if (response.getStatus().getCode() == Status.STATUS_OK) {
System.out.println(response.getResponse());
try {
org.json.JSONObject json = new org.json.JSONObject(response.getResponse());
result = Boolean.parseBoolean(((String) json.get("result")));
} catch (JSONException je) {
je.printStackTrace();
return false;
}
} else {
System.out.println("Call failed, code:" + response.getStatus().getCode());
return false;
}
} catch (ConnectorServiceException cse) {
cse.printStackTrace();
return false;
}
return result;
}
In this example I am using a simple example web script that echoes back your JSON and switches the result based on the value of the "echo" argument. So when it is called with "false", the JSON returns false and the evaluator returns false.
I should probably point out the name collision between the org.json.simple.JSONObject that the evaluate method expects and the org.json.JSONObject I am using to snag the result from the response JSON.

WebRequest.RegisterPrefix for http:// returns true, doesn't work

I'm trying to use WebRequest.RegisterPrefix to register a decorator IWebRequestCreate implementation with the intention being to add "debug" scenarios (like emulating different connectivity scenarios).
I'm using the Mango beta 2 SDK and the RegisterPrefix method always returns true when used with "http://" as a prefix (or "http" for that matter), but the registered IWebRequestCreate instance is not being used.
I can see from the documentation that it should return false for duplicates, but it doesn't seem to be functioning as documented.
Is there any other way of achieving what I'm after in a way that is transparent to consumers?
I'm using WebRequest.RegisterPrefix for unit testing, registering an IWebRequestCreate implementation for a prefix of test://, and this does work.
I found that after registering an IWebRequestCreate for http://, calling WebRequest.Create with an http:// uri would return a request created from the registered IWebRequestCreate, but calling WebRequest.CreateHttp would still return an HttpWebRequest.
The following code should verify this, and I'm using the Mango Beta 2 SDK (6-29-11):
public partial class MainPage : PhoneApplicationPage
{
public class FakeRequest : WebRequest
{
private Uri _uri;
public FakeRequest(Uri uri)
{
_uri = uri;
}
public override Uri RequestUri { get { return _uri; } }
}
public class FakeRequestFactory : IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
return new FakeRequest(uri);
}
}
// Constructor
public MainPage()
{
InitializeComponent();
// returns System.Net.Browser.ClientHttpWebRequest
var request1 = WebRequest.Create("http://www.foo.com");
// returns System.Net.Browser.ClientHttpWebRequest
var request2 = WebRequest.CreateHttp("http://www.foo.com");
// returns true
bool result1 = WebRequest.RegisterPrefix("http://", new FakeRequestFactory());
// returns FakeRequest
var request3 = WebRequest.Create("http://www.foo.com");
// returns System.Net.Browser.ClientHttpWebRequest
var request4 = WebRequest.CreateHttp("http://www.foo.com");
// returns false
bool result2 = WebRequest.RegisterPrefix("http://", new FakeRequestFactory());
// returns false, as per the note in the documention
bool result3 = HttpWebRequest.RegisterPrefix("http://", new FakeRequestFactory());
}
}
Hey I know this question is 2 years old but I came across the same problem. I think you'll find that WebRequest.RegisterPrefix() does return false if you try to register http: (notice the single colon, no forward slashes). If I ever find a workaround, I'll try to remember to update this post.
EDIT
In my particular case I wanted to throw out System.Net.FtpWebRequest and roll my own FTP client implementation (because the framework's implementation sucks).
In order to do that, I used reflection (and a bunch of late binding tricks) to get the arraylist of registered prefix and remove the ones that are linked to the internal System.Net.FtpWebRequestCreator class.
I'm not sure if all of these APIs are available for windows phone, but here's what I did:
Type webRequest = typeof(System.Net.WebRequest);
Assembly system = Assembly.GetAssembly(webRequest);
Type ftpWebRequestCreator = system.GetType("System.Net.FtpWebRequestCreator");
ArrayList prefixList = (ArrayList)webRequest.GetProperty("PrefixList", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null);
IEnumerator enumerator = prefixList.GetEnumerator();
while (enumerator != null && enumerator.MoveNext()) {
if (object.ReferenceEquals(enumerator.Current.Creator.GetType(), ftpWebRequestCreator)) {
prefixList.Remove(enumerator.Current);
if (System.Net.WebRequest.RegisterPrefix(enumerator.Current.Prefix, new CustomWebRequestCreator())) {
enumerator = null;
} else {
enumerator = prefixList.GetEnumerator();
}
}
}
// Now I can use Create() on the base class
System.Net.WebRequest myCustomWebRequest = System.Net.WebRequest.Create("ftp://example.com/public");
This works by finding all prefixes that are registered with FtpWebRequestCreator and replacing them with my own creator. It should be fairly straightforward to adapt this for http(s).
The phone only has a client stack so RegisterPrefix has no effect on the phone.

Resources