I am developing a Restful service using .net web API.
There are a few posts about input validation for the post request using model validation. However, I am wondering what is the best practice of doing the validation for Get request.
For example
public HttpResponseMessage Get(int id, string type)
{
// validation
if (id <= 500 & id >= 0) {
// invalid request
throw new HttpResponseException();
}
// type validation
if (type is not in a predefined allowed type list from database) {
// throw validation error
}
// more validation ... ...
// do something else
}
I would like to know what is the best place to put the validation logic in in .net web api framework.
The id validation is just an example and the validation logic could go quite complicated for some cases.
I don't want to create a class just for the id and put some custom validator attribute on the ID property. I think .net has a better support for that already.
You can use route constraints for this parameter
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { id = "#[1-500]+" } //this is not valid code. use correct regular expression to implement validation behavior you need.
);
Answer on comment. What u mean - complicated vlidation? You asked about GET request and the siplest way is use the route constraint. Another way is ActiontFilter. For example
public class SomeFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
string param = filterContext.ActionArguments["id"].ToString();
//do some validation stuff here. for example data anotations
var validator = new RangeAttribute(1, 500); //numeric range.
if (validator.IsValid(Convert.ToInt64(param)));
do valid//
//if u need validate entire model from post request try
if (!filterContext.ModelState.IsValid)
{
filterContext.Response = filterContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, filterContext.ModelState);
}
}
}
or google for "web api model validation"
Related
I'm working on an angular 2 app using a ASP.Net MVC backend. For this, the index.cshtml has included #Html.AntiForgeryToken(); so i get back a hidden input element with the Token as its value. In my frontend i can grab this token and use it for my requests. So far so good.
Now since user information is used to generate the token in the backend, i have to switch out the token on some occassions. The problem is described here in this question.
Now since i don't want to do a full page reload in my app i have to use a workaround. For this i created a partial view in the backend which just hands out this input using #Html.AntiForgeryToken(); as ActionResult with a simple method like this:
public ActionResult GetAntiForgery()
{
return PartialView("~/Views/Home/AntiForgery.cshtml");
}
After login / logout i call this function from my frontend and replace the value of my existing AntiForgery input element like this:
getAntiForgery(url:string) {
return this._http.get(url)
.map((response:any) => {
let originalXsrfElement = <HTMLInputElement>document.querySelector('[name=__RequestVerificationToken]');
let body = response._body;
if(body) {
let helperElem = document.createElement('div');
helperElem.innerHTML = body;
let helperInputElem = <HTMLInputElement>helperElem.firstChild;
originalXsrfElement.value = helperInputElem.value;
}
return response;
});
}
I can't even tell you what bugs me the most. I hate to make an extra request (but lets not dive into this here) but way more terrible for me is that i have to request something, get an html string back and have to extract the token string out of it.
If i were the backend guy, i would kill the frontend guy (me..) for even thinking about creating an extra partial view, creating an extra method and always doing two requests on login/logout instead of one.
Is there a better way? For example i would like to call a method which just hands out a JSON with the proper token instead of an HTML snippet. Even better, on existing JsonResult methods in the backend, i would like to add the new CSRF Token as a property.
I'm not a backend architect so there might be some stuff wrong in general how i do this, but my backend colleagues don't mind what i'm doing there so it shouldn't be so far off.
Any hints are appreciated.
You can use AntiForgery.GetToken in your action to set some property on the JSON being returned:
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
json.RequestVerificationToken = cookieToken + ":" + formToken;
Then, pass it back in the header of your next AJAX request:
$.ajax("/my/awesome/url", {
type: "post",
contentType: "application/json",
data: { ... },
dataType: "json",
headers: {
'RequestVerificationToken': requestVerificationToken
}
});
Since, it's no longer being handled by cookies, you can't just use the standard ValidateAntiForgeryToken attribute. You'll need to manually check for the header and validate it. However, you should be able to create a custom attribute you can use instead:
AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenFromHeaderAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
}
Then, just decorate your actions with [ValidateAntiForgeryTokenFromHeader] instead.
[Code adapted from http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks]
I am experiencing an issue with a refactoring of our payment processing action method (called by our 3rd-party online payment provider). We have a product controller with the [Authorize] and [RoutePrefix("products")] attributes at the class level, and action methods including the following:
Product(string contractNumber) with routing attribute [Route("{productCode}")]
MakePayment(string productCode, PaymentAmountType? amountSelection, decimal? amountValue) with routing attribute [Route("{productCode}")] and [HttpPost] attribute
ProcessPayment(string productCode, string result) with routing attribute [Route("{productCode}")]
Because our payment gateway needs to be able to call our ProcessPayment action before the visitor is redirected to that same URL, we've had to refactor that to a separate controller without the [Authorize] attribute. (We already have mechanisms to prevent double-crediting a payment.)
Before this refactoring, the MakePayment action method correctly formulated the correct return URL in the following call to Url.Action():
var rawCallbackUrl = Url.Action("ProcessPayment", new { productCode = productCode });
The ProcessPayment action method has now been moved out of the product controller and into a new controller, ExternalCallbackController, which has no attributes (let alone [Authorize]), in order to avoid having an HTTP 401 response returned to the payment provider.
The route attribute on ProcessPayment is now [Route("order-processing/{productCode}/process-payment")] to avoid clashing with the RoutePrefix on the product controller. All references to this updated action method are updated to specify the ExternalCallbackController.
Manually browsing to the URL causes the breakpoint set inside ProcessPayment to be hit, so the route apparently works successfully.
The problem is that in MakePayment, the following call returns null:
var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productCode = productCode });
Given that I am specifying both the controller and action method, why is Url.Action(...) not returning the expected URL in the form order-processing/{productCode}/process-payment?
From Day 1, our RegisterRoutes() method in RouteConfig has had attribute routing properly initialised with
routes.MapMvcAttributeRoutes();
How can I get the correct URL returned from the call to Url.Action(...)?
Doh - I've figured out what went wrong. Despite the sanitising of names in the source code (which were specific to our client), it turns out that there was a mismatch in the following call:
var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productCode = productCode });
and the ProcessPayment() action method.
This is akin to the following (note the use of productNumber instead of productCode):
var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productNumber = productNumber });
trying to reference the action method:
[Route("order-processing/{productCode}/process-payment")]
public ActionResult ProcessPayment(string productCode, string result)
{
...
}
It also turns out that I can also use the same prefix "products" instead of "order-processing", as MVC creates one Route per attribute route in the routing table. Hope that helps others stuck in a similar situation.
I got this Error : Url.Action return null.
public async Task<IActionResult> ForgotPassword(ForgotPassword forgotPassword)
{
if (ModelState.IsValid)
{
// Find the user by email
var user = await userManager.FindByEmailAsync(forgotPassword.Email);
// If the user is found AND Email is confirmed
if (user != null && await userManager.IsEmailConfirmedAsync(user))
{
// Generate the reset password token
var token = await userManager.GeneratePasswordResetTokenAsync(user);
// Build the password reset link
var passwordResetLink = Url.Action("ResetPassword", "Account",
new { email = forgotPassword.Email, token }, Request.Scheme);
ViewBag.PRL = passwordResetLink;
// Send the user to Forgot Password Confirmation view
return View("ForgotPasswordConfirmation");
}
return View("ForgotPasswordConfirmation");
}
return View(forgotPassword);
}
From my MVC application, I am trying to make a POST request to these sample end-points (actions) in an API controller named MembershipController:
[HttpPost]
public string GetFoo([FromBody]string foo)
{
return string.Concat("This is foo: ", foo);
}
[HttpPost]
public string GetBar([FromBody]int bar)
{
return string.Concat("This is bar: ", bar.ToString());
}
[HttpPost]
public IUser CreateNew([FromBody]NewUserAccountInfo newUserAccountInfo)
{
return new User();
}
Here's the client code:
var num = new WebAPIClient().PostAsXmlAsync<int, string>("api/membership/GetBar", 4).Result;
And here's the code for my WebAPIClient class:
public class WebAPIClient
{
private string _baseUri = null;
public WebAPIClient()
{
// TO DO: Make this configurable
_baseUri = "http://localhost:54488/";
}
public async Task<R> PostAsXmlAsync<T, R>(string uri, T value)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = new Uri(client.BaseAddress, uri);
var response = await client.PostAsXmlAsync<T>(requestUri, value);
response.EnsureSuccessStatusCode();
var taskOfR = await response.Content.ReadAsAsync<R>();
return taskOfR;
}
}
}
I have the following default route defined for the Web API:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
UPDATE
My code breaks into the debugger until the time the PostAsXmlAsync method on the System.Net.HttpClient code is called. However, no request shows up in Fiddler.
However, if I try to compose a POST request in Fiddler or try to fire a GET request via the browser to one of the API end-points, the POST request composed via Fiddler tells me that I am not sending any data and that I must. The browser sent GET request rightly tells me that the action does not support a GET request.
It just seems like the System.Net.HttpClient class is not sending the POST request properly.
One of the most usual problems is that you don't use the appropriate attribute.
Take into account that there are attributes for ASP.NET MVC and ASP.NET Web API with the same name, but which live in different namespaces:
For Web API you must use the one in System.Web.Http
For MVC, the one in System.Web.MVc
This is a very very usual error, and it affects to allkind of things that exist for both MVC and Web API. So you must be very careful when using something which can exists in bith worlds (for example filters, attributes, or dependency injection registration).
I experienced a similar problem (may not be same one though). In my case, I hadn't given name attribute to the input element. I only figured that out when fiddler showed no post data being sent to the server (just like your case)
<input id="test" name="xyz" type="text" />
Adding the name attribute in the input tag fixed my problem.
However, there is one more thing to note. WebAPI does not put form data into parameters directly. Either you have to create an object with those properties and put that object in the parameter of the post controller. Or you could put no parameters at all like this:
[Route("name/add")]
public async Task Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return;
}
var provider = PostHelper.GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
var clientId = result.FormData["xyz"];
...
Try changing the FromBody to FromUri.
If the parameter is a "simple" type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string.
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
Remove FromBody at all and don't make any restrictions in passing parameters (it can be passed at this time either in uri, query string or form submissions (which is kinda a similar to query strings)
[HttpPost]
public string GetFoo(string foo){...}
It will be implicitly parsed and passed.
I have a controller function which accepts a strongly typed model as parameter.
When i enter ANY url mapping to the controller but not on a specific action on the post request ,
then the controller executes this function instead of returning a 404 code.
When i Change the function's parameter to a primitive type variable then the problem does not occur. (i have tried using other strongly typed models as parameters and again the problem occurs)
Here's the function.
public class PhoneApiController : ApiController
{
[HttpPost]
public HttpResponseMessage RegisterApp(RegisterAppInfo appInfo)
{
var resp = Request.CreateResponse(HttpStatusCode.OK, new
{
Success = true,
AppId = 1000,
IdAlias = "QAUBC9",
appInfo = appInfo
});
return resp;
}
}
So when i enter for example
localhost:51464/Api/PhoneApi/Sajsdkasjdklajsasd
the function executes normally.!
I am using the default Route config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I don't know if this is a bug or i am doing something wrong.
The URI /Api/PhoneApi/Sajsdkasjdklajsasd does match your route template api/{controller}/{id} with {controller} matching PhoneApi and {id} matching Sajsdkasjdklajsasd. I assume you are making a POST to this URI. So, Web API is mapping your request to the action method RegisterApp with [HttpPost] in the controller class PhoneApiController.
As far as the junk stuff in the URI, it gets mapped to {id}. But your parameter is RegisterAppInfo, which is a complex type and that gets bound from request body and not the URI. That's why it works when you have the complex type. The simple types are bound from URI, query string.
If you have the action method as public HttpResponseMessage RegisterApp(string id, Abc appInfo), you will see that this id parameter gets populated with "Sajsdkasjdklajsasd".
For MVC 4.5 this is the only thing that works
There is currently a bug about this.
Below is a work around in order to get the following route types work
api/{controller}/ //Get All
api/{controller}/{Id} //Get for id
api/{controller}/{Id}/{Action}/ //Get all for action for controller with Id
you need to do the following.
Change your routing over to. (Note the default action..)
config.Routes.MapHttpRoute(
name : "DefaultAPi",
routeTemplate : "api/{controller}/{id}/{action}",
defaults: new
{
id = RouteParameter.Optional,
action = "DefaultAction"
}
);
In your controller change the base methods over to
[ActionName("DefaultAction")]
public string Get()
{
}
[ActionName("DefaultAction")]
public string Get(int id)
{
}
[ActionName("SpaceTypes")]
public string GetSpaceTypes(int id)
{
}
Now everything should work as expected..
Thanks to Kip Streithorst full this, for a full explanation
The way routing works in Web API is:
First it matches the URI against route template. At this stage, it's not looking at your controller actions
Then it looks for a matching controller
Then it looks for a method where (a) the action matches (POST in this case) and (b) every simple parameter type is matched with a value from the URI.
If there is a complex parameter type, it tries to read that from the request body.
By default, Web API tries to bind "simple" parameter types (like int) from the URI, and tries to read complex types from the request body.
See here for details: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
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
}