WebApi viewmodel validation not working - c#

I've been banging my head on trying to get ViewModels to validate with webapi 2.2
From the docs ..it should work:
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
namespace WebApplication3.Controllers
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
public class TestViewModel
{
[Required]
[EmailAddress]
[MinLength(3)]
[MaxLength(255)]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
public class ValuesController : ApiController
{
[ValidateModel]
[HttpGet]
public string Test(TestViewModel email)
{
if (ModelState.IsValid)
{
return "ok";
}
return "not ok";
}
}
}
With or without the ValidateModelAttribute it just returns "ok" all the time...
The ValidateModelAttribute is registered in WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ValidateModelAttribute());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Anyone have an idea whats going on here ? It's so much simpler to use DataAnnotations to prevalidate data.
Sample request:
http://localhost:55788/api/values/Test?email=ss
Returns: ok
Neither GET/POST changes anything

Where simple MVC controllers seem to have no problem, in this web api example we apparently have to specify [FromUri]
This works just fine
[HttpGet]
public string Test([FromUri]TestViewModel email)
{
if (ModelState.IsValid)
{
return "ok";
}
return "not ok";
}
With this code i can now also implement jsonP behavior
Also the custom ValidateModelAttribute becomes obsolete, though it can still be useful if you want to throw an exception systematically when a ViewModel is invalid. I rather just handle it in code to be able to return custom error objects.

Related

Posting a Json String to WebApi always end up with null parameter

Hi I have been trying to resolve this issue. I have a WebApi that accepts a Json String as a POST. The client is hitting our Webapi. However, "Result" is always NULL. I have been researching for several days but could not find an answer. I am using Postman to post the response to the WebApi. I am new to WebApi and this is my first post. Hope someone can shed some light on this. thank you
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new
HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Filters.Add(new BasicAuthenticationFilterAttribute());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
}
Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
OcrDataReceiverController.cs - "Result" is always NULL
[RoutePrefix("api/OcrDataReceiver")]
[Authorize]
public class OcrDataReceiverController : ApiController
{
[HttpPost]
[AllowAnonymous]
public HttpResponseMessage Post([FromBody]OcrResultModel result)
{
// Do Something
}
}
OcrResultModel.cs
[Serializable]
public class OcrResultModel
{
public string requestId { get; set; }
public string customerRequestId { get; set; }
}
Since I am new to stackoverflow, I can't post any images (postman) yet. In postman, I have Content-Type = application/json.
Body: Raw - JSON(application/json)
{"requestId":"2b20cba5-6715-4d3e-aea6-9e72088d87bc","customerRequestId":"Joseph123d"}
Remove Serializable from class. it will work.
As Json.NET serializer by default set the IgnoreSerializableAttribute to true, so it ignores the properties
public class OcrResultModel
{
public string requestId { get; set; }
public string customerRequestId { get; set; }
}

How to prevent HTTP 404 for a custom action in an odata controller?

I have an ASP.Net WebApi2 project hosting odata both ApiController and ODataController.
And I want to add a custom action in an ODataController.
I saw this seems to be achievable by either adding [HttpPost] attribute on the desired action, or by configuring the ODataConventionModelBuilder with a specific FunctionConfiguration when using the MapODataServiceRoute.
To distinguish between odata routes and webapi routes we use the following scheme :
odata : http://localhost:9292/myProject/odata
webapi : http://localhost:9292/myProject/api
I tried both these solution without success which all led to get an HTTP 404 result.
My custom action is defined as following:
public class SomeModelsController : ODataController
{
//...
[EnableQuery]
public IHttpActionResult Get()
{
//...
return Ok(data);
}
public IHttpActionResult MyCustomAction(int parameterA, int parameterB)
{
//...
return Json(data);
}
//...
}
So as you guessed it, the Get call on the controller perfectly work with odata. However the MyCustomAction is a bit more difficult to setup properly.
Here is what I have tried :
Setting an [HttpPost] attribute on MyCustomAction
[HttpPost]
public IHttpActionResult MyCustomAction(int parameterA, int parameterB)
{
//...
return Json(data);
}
I also tried decorating MyCustomAction with the [EnableQuery] attribute.
Also, I tried adding the [AcceptVerbs("GET", "POST")] attribute on the method without changes.
Configuring the ODataConventionModelBuilder
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder
{
Namespace = "MyApp",
ContainerName = "DefaultContainer"
};
// List of entities exposed and their controller name
// ...
FunctionConfiguration function = builder.Function("MyCustomAction ").ReturnsFromEntitySet<MyModel>("SomeModels");
function.Parameter<int>("parameterA");
function.Parameter<int>("parameterB");
function.Returns<MyModel>();
return builder.GetEdmModel();
}
Also tried decoration of MyCustomAction with [EnableQuery], HttpPost and [AcceptVerbs("GET", "POST")] attributes.
I still get HTTP 404 result.
My query url is as follow:
http://localhost:9292/myProject/odata/SomeModels/MyCustomAction?parameterA=123&parameterB=123
I also tried to POST parameters on
http://localhost:9292/myProject/odata/SomeModels/MyCustomAction with the same result. Actually with or without parameters I get HTTP 404 status.
I've created a working example from scratch with Visual Studio 2017.
If you want more info you can read this tutorial:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-actions-and-functions
Create a new ASP.Net Web Application (no .Net Core)
Choose WebApi Template
Install from NuGet the package Microsoft.AspNet.OData (I have used v. 6.0.0)
Create a simple model class into Models folder
TestModel.cs
namespace DemoOdataFunction.Models
{
public class TestModel
{
public int Id { get; set; }
public int MyProperty { get; set; }
public string MyString { get; set; }
}
}
Configure WebApiConfig
WebApiConfig.cs
using DemoOdataFunction.Models;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
namespace DemoOdataFunction
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "MyNamespace";
builder.EntitySet<TestModel>("TestModels");
ActionConfiguration myAction = builder.EntityType<TestModel>().Action("MyAction");
myAction.Parameter<string>("stringPar");
FunctionConfiguration myFunction = builder.EntityType<TestModel>().Collection.Function("MyFunction");
myFunction.Parameter<int>("parA");
myFunction.Parameter<int>("parB");
myFunction.ReturnsFromEntitySet<TestModel>("TestModels");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel()
);
}
}
}
Create the controller TestModelsController into Controllers folder
TestModelsController.cs
using DemoOdataFunction.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
namespace DemoOdataFunction.Controllers
{
public class TestModelsController : ODataController
{
IQueryable<TestModel> testModelList = new List<TestModel>()
{
new TestModel{
MyProperty = 1,
MyString = "Hello"
}
}.AsQueryable();
[EnableQuery]
public IQueryable<TestModel> Get()
{
return testModelList;
}
[EnableQuery]
public SingleResult<TestModel> Get([FromODataUri] int key)
{
IQueryable<TestModel> result = testModelList.Where(t => t.MyProperty == 1);
return SingleResult.Create(result);
}
[HttpPost]
public IHttpActionResult MyAction([FromODataUri] int key, ODataActionParameters parameters)
{
string stringPar = parameters["stringPar"] as string;
return Ok();
}
[HttpGet]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All, MaxExpansionDepth = 2)]
public IHttpActionResult MyFunction(int parA, int parB)
{
return Ok(testModelList);
}
}
}
Edit Web.config changing the handlers section in system.webServer
web.config
<system.webServer>
<handlers>
<clear/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*"
verb="*" type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
[...]
</system.webServer>
That's all.
This is the request for MyAction:
POST
http://localhost:xxxx/odata/TestModels(1)/MyNamespace.MyAction
{
"stringPar":"hello"
}
This is the request for MyFunction:
GET
http://localhost:xxxx/odata/TestModels/MyNamespace.MyFunction(parA=1,parB=2)
I am using HTTP POST with route on the controller functions like below:
[HttpPost]
[Route("{application}/{envName}/date/{offset}")]
[ResponseType(typeof(DateInfo))]
public async Task<IHttpActionResult> SetDateOffsetForEnvironmentName(string application, string envName, string offset)
{
}
can you try setting the route on the function and then call the post method on it like this:
POST /status/environments/ATOOnline/PTH/date/0
Also try and capture a request through Fiddler and see what is being passed.

Web api controller selection by route

I have two different class library that contains same name controllers.
namespace OldApiService{
public class GreetingController: ApiController{
public string Get(){ return "hello from old api"; }
}
}
namespace NewApiService{
public class GreetingController: ApiController{
public string Get(){ return "hello from new api"; }
}
}
And I have a main We Api applciation that contains Route and other helper classes. This application references NewApiService and OldApiService assemblies.
namespace MyApi {
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{api}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
}
I want to select a controller that specified url parameter.
http://localhost:4035/api/old/greeting will use OldApiService Controller
http://localhost:4035/api/new/greeting will use NewApiService Controller
I tried to change url route settings but not worked. Duplicated controller error occurred.
Is there any way to override the controller selection mechanism. Simply I will get route value (old or new) and select the controller from specified namespace.
Multiple types were found that match the controller named 'greeting'.
This can happen if the route that services this request
('api/{version}/{controller}/{id}') found multiple controllers defined
with the same name but differing namespaces, which is not supported.
The request for 'greeting' has found the following matching
controllers: OldApiService.GreetingController
NewApiService.GreetingController
I think this is an important issue for asp.net web api.
Old library
namespace OldApiService{
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.config.MapHttpAttributeRoutes();
}
}
[RoutePrefix("api/old/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from old api"; }
}
}
Other library
namespace NewApiService{
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.config.MapHttpAttributeRoutes();
}
}
[RoutePrefix("api/new/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from new api"; }
}
}
Start up
namespace MyApi {
public class Startup {
public void Configuration(IAppBuilder appBuilder) {
var config = new HttpConfiguration();
//Map attribute routes
OldApiService.WebApiConfig.Register(config);
NewApiService.WebApiConfig.Register(config);
//convention-based routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{api}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
}
I would use the RoutePrefix/Route attributes.
namespace OldApiService{
[RoutePrefix("api/old/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from old api"; }
}
}
namespace NewApiService{
[RoutePrefix("api/new/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from new api"; }
}
}

WebApi routing - many GET methods

I have the following (standard) WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and the following api controller:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/Books
[Route("")]
public IQueryable<string> GetBooks()
{
return null;
}
// GET api/Books/5
[Route("{id:int}")]
public async Task<IHttpActionResult> GetBook(int id)
{
return Ok();
}
[Route("{id:int}/details")]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
return Ok();
}
[Route("abc")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}
[Route("~api/authors/{authorId}/books")]
public IQueryable<string> GetBooksByAuthor(int authorId)
{
return null;
}
}
It found appropriate method when I call
api/books
api/books/1
api/books/1/details
but it can't find api/books/abc.
If I change [Route("abc")] to [Route("{genre}")] it works (pass abc as genre parameter).
But I need to have many GET methods with different names.
What did I do wrong?
Try
// GET api/Books/genres/horror
[Route("genres/{genre}")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}
Or even
// GET api/genres/horror/books
[Route("~api/genres/{genre}/books")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}

ASP.NET Web Api Routing Broken

I am unable to get basic routing to work in my asp.net web api project. I have followed examples on asp.net (http://www.asp.net/web-api/overview/web-api-routing-and-actions) and I have searched throughout stackoverflow in an attempt to find a solution. Whatever examples I have tried, I cannot get attribute routing to work.
This is my controller:
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
[Route("")]
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
[Route("{id:int}")]
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
This is my Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
This is my WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
No matter what I attempt, I end up either with a 404 or as in the case of the code above, I get the message
No HTTP resource was found that matches the request URI
'http://localhost:2442/api/employee/1'.
No action was found on the controller 'Employee' that matches the
request.
with or without the integer parameter.
Either use the attribute routing for your controller, or don't use it all. That means you need to decorate your controller with RoutePrefix instead of relying on the configured routes.
[RoutePrefix("api/employee")
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
[Route("")]
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
[Route("{id}")]
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
or in the below example, we rely on the defined route instead of using attribute routing.
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
If you mix and match, it confuses things.
Did you try putting the RoutePrefix attribute on your class like this:
[RoutePrefix("api/employee")]
public class EmployeeController : ApiController

Categories

Resources