I'm struggling getting Swagger to present my ServiceStack service correctly.
I would like to see an UserId string as a form parameter and a PrivateCustomer object as a body parameter, but keep getting a body parameter with BOTH the UserId and the PrivateCustomer, despite UserId also appearing as a separate input field.
Here's my code:
And here's the result in Swagger:
How do I get rid of the UserId in the body?
Thanks a lot!
The [Api*] annotations are only for documenting your API, they do not impact your API's behavior, e.g. the request body is always expected to be the entire Request DTO and you can't have both "form" and "body" parameter types sent at the same time, i.e. there's only 1 Request Body and when using "form" only the form variables will be sent.
If you wanted to separate them you could add UserId to the query string and exclude them from appearing in the model schema with:
[Route("/CreatePrivateCustomer", "POST")]
public class CreatePrivateCustomerRequest
{
[ApiMember(IsRequired = true, ParameterType = "query", ExcludeInSchema = true)]
public string UserId { get; set; }
[ApiMember(IsRequired = true, ParameterType = "model")]
public PrivateCustomer Customer { get; set; }
}
This will separate the variables and send UserId in the queryString and the request of the DTO in the Request Body as JSON, e.g:
POST /CreatePrivateCustomer?UserId=1
Content-Type: application/json
{"Customer":{"CustomerNumber":1,...}}
Although generally if you want required parameters separated from the Request Body you would put them in the path, e.g:
[Route("/CreatePrivateCustomer/{UserId}", "POST")]
public class CreatePrivateCustomerRequest
{
[ApiMember(IsRequired = true, ParameterType = "path", ExcludeInSchema = true)]
public string UserId { get; set; }
[ApiMember(IsRequired = true, ParameterType = "model")]
public PrivateCustomer Customer { get; set; }
}
and if you don't want the PrivateCustomer properties nested you would add them directly on the Request DTO, e.g:
[Route("/CreatePrivateCustomer/{UserId}", "POST")]
public class CreatePrivateCustomerRequest
{
[ApiMember(IsRequired = true, ParameterType = "path", ExcludeInSchema = true)]
public string UserId { get; set; }
public int CustomerNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Related
I have trying to pass a query string parameter to my JsonResult action in the controller. I keep getting the following error:
Value cannot be null.
Parameter name: String
I need the task_id from this url:
TaskIn?task_id=33
In my view I have tried (fails with same error):
#model TaskingSystem.Models.AcceptTasksViewModel
#{string task_id = #Request.QueryString["task_id"];}
#Html.HiddenFor(m => m.task_id)
In controller:
public JsonResult TasksExist(string email, string task_id)
{
int tasks_id = int.Parse("task_id");
return Json(db.Tasks_Validate.Any(e => e.Email == email && e.task_id == tasks_id), JsonRequestBehavior.AllowGet);
}
My model:
public class AcceptTasksViewModel{
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email:")]
//Using Remote validation attribute
[Remote("TasksExist", "Task_Results", ErrorMessage = "Email does not exists in database. Please try a different email address.")]
public string email { get; set; }
public int task_id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I have also tried just passing straight into the action using this but it still fails.
int tasks_id = int.Parse(Request.Params["task_id"]);
To pass the task_id along with the email to your TasksExist(string email, string task_id) method, you have to mention task_id in AdditionalFields property of Remote attribute as follows:
[Remote("TasksExist", "Task_Results",AdditionalFields = "task_id" ErrorMessage = "Email does not exists in database. Please try a different email address.")]
public string email { get; set; }
I have model class as below:
public class HomeFilterModel {
public string CompanyName { get; set; }
public string Country { get; set; }
public DateRange Period { get; set; }
}
public class DateRange {
public DateTime? From { get; set; }
public DateTime? To { get; set; }
}
On client side, the data collected as below object:
{
"CompanyName":"",
"Country":"Canada",
"Period":{
"from":"2018-08-05T04:00:00.000Z",
"to":"2018-08-09T04:00:00.000Z"
}
}
It's converted to query string by $.param function and added to URL.
?CompanyName=&Country=Canada&Period%5Bfrom%5D=2018-08-05T04%3A00%3A00.000Z&Period%5Bto%5D=2018-08-10T04%3A00%3A00.000Z
The controller action is as below:
[HttpGet]
public ActionResult Index(HomeFilterModel filter) {
return View(filter ?? new HomeFilterModel());
}
The model data filter can get CompanyName and Country values correctly, but Period.From and Period.To are always NULL. I wonder if I have to make customized ModelBinder for this model class specifically? Or I should use different function other than $.param to construct query string in URL?
UPDATED:
If query string is changed to
?CompanyName=&Country=Canada&Period.from=2018-08-05T04%3A00%3A00.000Z&Period.to=2018-08-10T04%3A00%3A00.000Z
controller action can get all data correctly. So Period[from] is not acceptable by default model binder, while Period.from is.
Anyone has the simplest and best solution for this?
In need of some advice, I am trying to create a register/login section on a SPA project I am working on.
I am using AngularJS for the front end and MVC Web API for the back end.
Problem I am having is my model is showing as null when the Web API's POST method is hit.
Here is my code:
AngularJS Controller
SugarGlidersMain.controller('RegisterController', ['$scope', 'UserService', '$location', '$rootScope', 'FlashService', function ($scope,UserService, $location, $rootScope, FlashService) {
$scope.user = [{ID:'',Username:'',Password:'',Email:'',LastLogin:'',Role:'User', FirstName:'',LastName:''}]
$scope.register = function() {
var data = $scope.user;
UserService.Create($scope.user)
.then(function (response) {
if (response.success) {
FlashService.Success('Registration successful', true);
$location.path('/login');
} else {
FlashService.Error(response.message);
}
});
}
Note: Firstname, Lastname, Password and Email are bound to input fields in the html
AngularJS service
function Create(user) {
return $http.post('/api/User', user).then(handleSuccess, handleError('Error creating user'));
}
Note: user contains data when being sent to the Web API
WebAPI
// POST api/user
public void Post([FromBody]UserModel user)
{
string firstname = user.FirstName;
string lastname = user.LastName;
}
MVC Model
public class UserModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Email { get; set; }
public DateTime? LastLogin { get; set; }
public string Role { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Does the data structure matter when passing a model to web API eg could I simply pass over Username, Email and Password without the other properties to the Web API and still use UserModel?
Thank you in advance.
In you current case you are passing $scope.user array to post but the thing is though you passed whole data in the array first element controller method won't understand that object, because its different than what actually the method is expecting. So for getting correct object on server you should pass correct JSON.
When you are passing object to API it should pass single user object rather than passing the whole array
UserService.Create($scope.user[0])
OR
Better change user object declaration to object
$scope.user = {ID:'',Username:'',Password:'',Email:'',LastLogin:'',Role:'User', FirstName:'',LastName:''};
I'm trying to make Json .NET Ignore a property by using the Json Ignore Attribute when clients GET the object but I want to be able to receive that property when a client is POST'ing
In example I have:
When the client POSTs data, password should be sent:
{"email":"email#domain.com","password":"P#ssW0rd1!","firstname":"Joe","lastname":"Doe"}
However, when the client GETs the same object, I should Ignore the Password:
{"email":"email#domain.com","firstname":"Joe","lastname":"Doe"}
Class:
public class User
{
public User()
{
this.JoinDate = DateTime.UtcNow;
this.IsActive = false;
}
public int Id { get; set; }
[Required(ErrorMessage = "Email is required!")]
public string Email { get; set; }
[JsonIgnore]
public string HashedPassword { get; set; }
[Required(ErrorMessage = "Password is required!")]
public string Password { get; set; }
public DateTime JoinDate { get; set; }
[Required(ErrorMessage = "First Name is required!")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required!")]
public string LastName { get; set; }
}
Any ideas, suggestions, comments???
In your scenario here, I would recommend you to split your User class into 2 separate model classes:
Login class, which has the login information (i.e. Email, Password)
UserInfo class, which has the rest of the metadata about the User (i.e. FirstName, LastName)
This way, we are not depending on the serializer to hide sensitive data.
You could use [IgnoreDataMember] attributes and the out-of-box XML and JSON formatters will support them, but there is no guarantee that any other custom formatter registered will support it.
Note that [JsonIgnore] is only supported in the JSON formatter but not the XML formatter.
In the below code I cannot pass the username to the remote validation function:
public string UserName { get; set; }
public class Numbers
{
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
}
public List<Numbers> NumberList { get; set; }
This is a simple example but I would like to pass additional fields from the same model within a list but I cant seem to access anything outside the scope of the public class.
Do I need to pass the rest of the model into the list in some way to achieve this or am I doing something wrong here?
The AdditionalFields parameter in the remote validation attribute need to be in the same class as the object being validated.
..edit..
public class Numbers
{
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
}
..edit after comments..
It looks like what you want to do is validate that all the numbers are unique for a Username. Try this:
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public List<String> NumberList { get; set; }
In your NumberExists Action take a List of Strings rather than only 1 string. This will let you validate your whole array all at once.
Public ActionResult NumberExists(List<String> NumberList, String UserName){
//Validate list is unique for username
}
UserName Property should be in the same class of the additionalNumbers property:
public class NumbersViewModel
{
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
public List<Numbers> NumberList { get; set; }
}