Routes having conflict in URL rewriting or configuring route in ASP.NET Web API - asp.net

I am developing an ASP.Net MVC application. In my project I am providing REST API for mobile devices. But I am having a problem with configuring route for URLs of my rest API in Web Api. I am using Web Api 2 and MVC 5. My problem is routes having conflict. Request cannot choose the correct route even I configured it clearly.
I have AccountController like this.
[RoutePrefix("v1")]
public class AccountController : ApiController
{
public UserManager<ApplicationUser> UserManager { get; private set; }
[HttpPost]
public async Task<IHttpActionResult> Register(RegisterBM model)
{
}
[HttpPost]
public IHttpActionResult Login(LoginBM model)
{
}
}
I configured routes in WebApiConfig like this
config.Routes.MapHttpRoute(
name: "",
routeTemplate: "api/v1/account/register",
defaults: new { controller = "Account", action = "Register" ,model = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "",
routeTemplate: "api/v1/account/login",
defaults: new { controller = "Account", action = "Login", model = RouteParameter.Optional }
);
These are my model classes
public class LoginBM
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
public class RegisterBM
{
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
[MaxLength(70)]
[UniqueAccountEmail(ErrorMessage = "Email is alreay taken")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
public string Password { get; set; }
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
This is error I get when I make request to http://localhost:50489/api/account/login
What is wrong with my route configuration? How can I fix that?

The URL you are testing http://localhost:50489/api/account/login does not match the routeTemplate of the route you have provided api/v1/account/login, namely you are missing v1 from the URL. Your test is apparently reaching another route, which is causing this exception.
Since Web API and MVC are separate frameworks, each has their own controllers. URLs must be unique for the application, but route values are not shared between them. So you can use the same set of route values in both frameworks (same names for controllers and actions) provided you use unique URLs across them both. Generally, this is done by prefixing the Web Api URL with /api (which you have already done). But if you put a version in the routeTemplate, you must also use it in the actual URL or you will not reach the controller action.

Related

Swagger UI doesn't render body parameter field for my complex type parameter in GET action of my Controller

I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.

.NET Core OData Action Parameter Null

My Odata Action Parameters are not resolving / deserializing.
I am using dotnet core 2.2 to surface an OData controller.
I need to implement an unbounded action. The action parameter (UserDto userDto) is not being deserialized by the OData routing engine:
[AllowAnonymous]
[HttpPost]
[ODataRoute(Routing.Endpoints.UserRoutes.AUTHENTICATE)]
public async Task<IActionResult> Authenticate(UserDto userDto)
{
var user = await _userService.Authenticate(userDto?.Username, userDto?.Password);
if (user == null)
return BadRequest("Username or password is incorrect");
var dto = Mapper.Map<UserDto>(user);
return Ok(dto);
}
Here is my configuration:
app.UseMvc(routeBuilder =>
{
var odataBuilder = new ODataConventionModelBuilder(app.ApplicationServices);
odataBuilder.EnableLowerCamelCase();
odataBuilder.EntitySet<BookDto>(nameof(Book));
odataBuilder.EntitySet<UserDto>(nameof(User));
var authenticate = odataBuilder.Action(Routing.Endpoints.UserRoutes.AUTHENTICATE);
authenticate.Parameter<UserDto>("userDto");
routeBuilder.Select().Expand().Filter().OrderBy().Count().MaxTop(int.MaxValue);
routeBuilder.MapODataServiceRoute("odata", string.Empty, odataBuilder.GetEdmModel());
});
Here is the UserDto:
public class UserDto
{
[Key] public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Token { get; set; }
}
When I post:
The action is resolved by the routing engine - but the parameter does not have the "Username" and "Password" values:
If I use the [FromBody] attribute on the parameter - the "userDto" parameter is null:
The schema seems correct:
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
<Action Name="authenticate">
<Parameter Name="userDto" Type="ExampleApi.Dto.UserDto"/>
</Action>
<EntityContainer Name="Container">
<EntitySet Name="Book" EntityType="ExampleApi.Dto.BookDto"/>
<EntitySet Name="User" EntityType="ExampleApi.Dto.UserDto"/>
<ActionImport Name="authenticate" Action="Default.authenticate"/>
</EntityContainer>
</Schema>
I have tried following this: Action Parameter Support
And even Microsofts version (albeit dated): Actions and Functions in OData
Been banging my head on this all day...
You could use simple WebApi attributes only to achieve authentication
public class UserController : ODataController
{
[AllowAnonymous]
[HttpPost("user/auth")]
public async Task<IActionResult> Authenticate([FromBody] UserDto userDto)
{
return Ok(userDto);
}
}

No resource found error while calling WEB API

I created a WEB API for getting some list . I created another API for login which is working fine. But whenever call the API which provide me some list, it will show error as
No HTTP resource was found that matches the request URI 'http://localhost:85476/API/EmployeeMobile/GetEmployeeForMobile,
No action was found on the controller 'EmployeeMobile' that matches the request.
My controller:
public class EmployeeMobileController : ApiController
{
private PCommon _pCommon;
IEmployeeBusiness _employeeBusiness;
Mapper _mapper;
public EmployeeMobileController()
{
_pCommon = new PCommon();
_employeeBusiness = new EmployeeBusiness();
_mapper = new Mapper();
}
[HttpPost]
public string GetEmployeeForMobile(string userID, string connectionString)
{
DataSet dsEmployee = _employeeBusiness.GetAllEmployeeForMobile(Guid.Parse(userID), connectionString);
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
object routes_list = (object)json_serializer.DeserializeObject(JsonConvert.SerializeObject(dsEmployee.Tables[0]));
return JsonConvert.SerializeObject(routes_list);
}
}
My WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
My PostMan Calling details
http://localhost:61557/API/EmployeeMobile/GetEmployeeForMobile
Body: {"userID":"fc938df0-373c559f","connectionString":"Data\tSource=WIN\\MSSQLSERVER2016;Initial\tCatalog=PDefault;User\tID=db;Password=db123"}
Please help me to recover this problem.
Please note, WebAPI doesn't support binding of multiple POST parameters. You are defining your POST like a GET method. Change it to return a single Model
Your model should look like following.
public class Input
{
public string uSerID { get; set; }
public string connectionString { get; set; }
}
Now change the POST method like
[HttpPost]
public string GetEmployeeForMobile(Input request)
{
//Your implementation here
}
and you JSON for the post should look like following.
{uSerID:"abc", connectionString :"xyz"}

ASP.Net OData with string keys

I am trying to use ASP.Net OData v4 (e.g. ODataController) to allow access where the key is a string. 95% of the examples out there use an integer as a key and the couple of posts I've found that discuss the steps to use a string as the key aren't working for me.
In all cases, I am trying to access my resource with the following URL:
/api/ContactTypes('Agency')
Optimistically, I started with just changing the type of the key from int to key:
public SingleResult<ContactType> Get([FromODataUri] string key)
But I get a 404 response. Changing the URL to an integer, /api/ContactTypes(1) does "work" in that it routes to the correct method and that the key is a string type, but obviously, that doesn't help me. This is the scenario described in this post: How to get ASP.Net Web API and OData to bind a string value as a key? except that that post implies that accessing the URL the way I am should work (and also is for OData v3).
After further searching, I found this article: https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/ which basically says that you have to decorate the Get method with an explicit routing:
[ODataRoute("({key})")]
public SingleResult<ContactType> Get([FromODataUri] string key)
If I do that alone, though, I get "The path template '({key})' on the action 'Get' in controller 'ContactTypes' is not a valid OData path template. Empty segment encountered in request URL. Please make sure that a valid request URL is specified."
The comments in this post (https://damienbod.com/2014/06/16/web-api-and-odata-v4-crud-and-actions-part-3/) suggest that I need to decorate the Controller with an ODataRoutePrefix:
[ODataRoutePrefix("ContactTypes")]
public class ContactTypesController : ODataController
That seems counter-intuitive since I do not have anything ASP.Net should be confusing. My controller name is already following convention and I have no Web API controllers that could be confusing it.
Regardless, it does seem to "fix" the issue in that the error goes away, but then I am right back at square one (e.g. only integer values can be passed in the URL).
What am I missing?
Full controller code:
[Authorize]
[ODataRoutePrefix("ContactTypes")]
public class ContactTypesController : ODataController
{
PolicyContext _Db;
public ContactTypesController(PolicyContext db)
{
if (db == null)
throw new ArgumentNullException("db");
this._Db = db;
}
public ContactTypesController() : this(new PolicyContext())
{
}
protected override void Dispose(bool disposing)
{
_Db.Dispose();
base.Dispose(disposing);
}
[EnableQuery]
[ODataRoute()]
public IQueryable<ContactType> Get(ODataQueryOptions options)
{
return _Db.ContactType;
}
[EnableQuery]
[ODataRoute("({key})")]
public SingleResult<ContactType> Get([FromODataUri] string key)
{
IQueryable<ContactType> result = _Db.ContactType.Where(p => p.ContactTypeKey == key);
return SingleResult.Create(result);
}
Full WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
builder.EntitySet<ContactType>("ContactTypes");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "api",
model: builder.GetEdmModel()
);
}
1.If in your EdmModel, the string property is key, then no ODataRoute is need, for example:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
ConventionModelBuilder will use property named "Id" as the key, or you should specify it's a key like:
public class Product
{
[Key]
public string StringKey { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
Then the call like localhost\api\Products('test') should just go to
public SingleResult<Product> GetProduct([FromODataUri]string key)
2.If you already have a int as a key, but you want use string as another key, then you should try this feature: http://odata.github.io/WebApi/#04-17-Alternate-Key , and you can call like:
localhost\api\Products(StringKey='test')

ASP.NET MVC API model not parsing

I am having some problems parsing my model in ASP.NET MVC API
This is my API controller:
public class UserController : ApiController
{
// Hent liste af personer
public IEnumerable<UserModel> Get()
{
return new UserModel[] { new UserModel(), new UserModel() };
}
// Hente enkelt person
public UserModel Get(int id)
{
return new UserModel();
}
// Opret person
[ValidationActionFilter]
public CreateUserRespose Post([FromBody]UserModel model)
{
CreateUserRespose rs = new CreateUserRespose();
return rs;
}
// Rediger person
public UserModel Put(int id, [FromBody]UserModel model)
{
return new UserModel();
}
// Slet person
public UserModel Delete(int id)
{
return new UserModel();
}
}
}
And the UserModel:
public class UserModel
{
[Required]
[StringLength(500)]
public String FristName { get; set; }
[Required]
[StringLength(500)]
public String LastName { get; set; }
[Required]
[StringLength(250)]
public String Email { get; set; }
[Required]
public String MatrikelId { get; set; }
}
When I call though Fiddler to the Post command with the following body
FirstName=Fistname MiddleName&LastName=SomeName&Email=email#email.us&MatrikelId=1234
Will the action Post be called, but the model is null, and ModelState.IsValid is true, the same happens if I send no data with the body!
What am I doing wrong here?
Update:
I have tryed sending the data as json instead
Fiddler:
User-Agent: Fiddler
Host: localhost:51268
Content-Length: 102
Content-type: application/json
{"FristName":"Kasper asdasd","LastName":"asdasdasd","Email":"asdaasd#asdasd.us","MatrikelId":"132456asd"}
But should the model state not be invalid when the model is null?
The ASP.NET Web API is using content negotiation process in order to decide which MediaTypeFormatter to use for deserializing the body of the request. For the typical POST request it will check for Accept and Content-Type headers. If none is present it will use the first MediaTypeFormatter on the list (by default it is JsonMediaTypeFormatter).
In your case Web API was unable to determine the proper MediaTypeFormatter. Adding a Content-Type header with value of application/x-www-form-urlencoded to the request should resolve the issue.
If you want to get more detailed knowledge regarding Formatters, Model Binding and Content Negotiation in ASP.NET Web API I would suggest following reading:
Designing Evolvable Web APIs with ASP.NET -> Chapter 8. Formatters and Model Binding (you should look very close at the entire book if you are interesed in learning ASP.NET Web API)
Everything you want to know about ASP.NET Web API content negotiation

Resources