I'm using JsonPatchDocument with ASP.NET 4.5 and Web Api. My controller looks like this:
[HttpPatch]
[Route("MyRoute/{PersonItem1}/{PersonItem2}/")]
public IHttpActionResult ChangePerson([FromHeader]Headers, [FromBody]JsonPatchDocument<PersonDto> person)
{
// Do some stuff with "person"
}
And PersonDto:
public class PersonDto
{
public string Name { get; set; }
public string Email { get; set; }
}
Now, I may send a PATCH request that is something like:
{
"op": "op": "replace", "path": "/email", "value": "new.email#example.org"
}
Now let's say I add some data annotations:
public class PersonDto
{
public string Name { get; set; }
[MaxLength(30)]
public string Email { get; set; }
}
What is the best way to ensure this validation is honored without writing additional validation. Is it even possible?
There is the simple method:
Get your object from your repository.
Deep copy the object so you have object A and B.
Apply the change with person.ApplyUpdatesTo(objB).
Create an extension method to validate the difference between object A and B.
If the validation is good proceede, if not throw an error.
This would catch if the client was attempting to modify immutable fields or if the new information in object B violates your constraints.
Note that this is not a great solution in that you would have to change your code in two places if you happen to change your constraints.
Related
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.
I am trying to send multiple files along with some data for every file. This is my model:
public class FileDTO
{
[Required]
public IFormFile File { get; set; }
[Required]
public string CategoryName { get; set; }
[Required]
public string CategoryDescription { get; set; }
public string Detail { get; set; }
}
This is my controller:
[HttpPost("Upload/{id:int}")]
public async Task<IActionResult> Upload(int id, IEnumerable<FileDTO> appFileDTOs)
{
...
}
Is this even a correct way to do so? How do I send such a request in Postman to simulate it?
Thanks in advance!
Edit
I tried it like this in Postman:
Everything submits correctly besides the image. For some reason the image is always null...
[] represents collection/dictionary index while dot(.) represents there's a property.
So you should rename all the field names with the dot representation.
For example, change
appFileDTOs[0][File]
to
appFileDTOs[0].File
Demo
try this it may help you,
send from formData.
in model key send value as
[
{
"CategoryName":"Category1",
"CategoryDescription ":"Category1 Description",
"Detail":"Details "
},
{
"CategoryName":"Category2",
"CategoryDescription ":"Category2 Description",
"Detail":"Details2"
}
]
and for file send first file as file1 and second file as file2;
In server side , remove IEnumerable of FileDTO appFileDTOs from method name.
get value of model as
var data = JsonConvert.DeserializeObject<List<FileDTO>>(Request.Form["model"]);
simillary for file
var fileUpload1 = Request.Form.Files["file1"];
var fileUpload2 = Request.Form.Files["file2"];
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')
In a GET request, I can create a mapping from my back-end model to a customized DTO with AutoMapper with ease. However, I have some concerns when using AutoMapper with POST requests.
Suppose a user orders a product online, he sends the server a POST request with some required data. The fact is, not every piece of data in the back-end model is sent by the user. Let's say the ID of the Order is a GUID which is generated automatically when the entry is inserted into the database; or maybe there are other properties which are auto-incremented. All of these cannot-be-mapped properties lead to a lot of .ForMember(dest => dest.myProperty, opt => opt.Ignore()) chains, and extra handling on the mapped instance after var mappedInstance = Mapper.Map<PostDTO, BackEndModel>(postDTO).
Is AutoMapper not designed for the aforementioned scenario? What is the practice for handling the model-mapping process if the back-end model is much more complex than the DTO?
Update
public class MultipleChoiceQuestion
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid MultipleChoiceQuestionId { get; set; }
[Required]
public string Question { get; set; }
[Required]
public ICollection<PossibleChoice> PossibleChoices { get; set; }
}
public class PossibleChoice
{
[Key, Column(Order = 1), ForeignKey("MultipleChoiceQuestion")]
public Guid MultipleChoiceQuestionId { get; set; }
[Key, Column(Order = 2)]
public int ChoiceIndex { get; set; }
[Required]
public string AnswerText { get; set; }
public MultipleChoiceQuestion MultipleChoiceQuestion { get; set; }
}
The user sends a request to create a new question. Only 2 fields are sent.
{
"Question": "How are you?",
"Answers": [
{ "Text": "I am fine." },
{ "Text": "Feeling bad." }
]
}
Properties that are missing at this stage:
MultipleChoiceQuestionId
Generated after the insertion
ChoiceIndex
Auto-incremented from 1 up to the number of answers available
Without manual mapping, how to handle this situation with AutoMapper?
1- Define your DTOs to be something like this:
public class MultipleChoiceQuestionDto
{
// This property could stay here, because you may need to use the same DTO for update (PUT),
// which means you need the Id to distinguish and validate the DTO data against the URL id
//public Guid MultipleChoiceQuestionId { get; set; }
public string Question { get; set; }
public ICollection<PossibleChoiceDto> PossibleChoices { get; set; }
}
public class PossibleChoiceDto
{
// This can go from this dto, because this DTO is a child dto for its parent.
//public Guid MultipleChoiceQuestionId { get; set; }
// This property could stay here, because you may need to use the same DTO for update (PUT),
// which means you need the Id to know which Choice was updated.
//public int ChoiceIndex { get; set; }
public string AnswerText { get; set; }
}
2- You create a mapping between the entity and the corresponding Dto like this, make sure you call this code from the global.asax file.
Mapper.CreateMap<MultipleChoiceQuestion, MultipleChoiceQuestionDto>();
Mapper.CreateMap<MultipleChoiceQuestionDto, MultipleChoiceQuestion>()
.ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore()); // you force automapper to ignore this property
Mapper.CreateMap<PossibleChoice, PossibleChoiceDto>();
Mapper.CreateMap<PossibleChoiceDto, PossibleChoice>()
.ForMember(m => m.MultipleChoiceQuestion, e => e.Ignore()) //
.ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore())
.ForMember(m => m.ChoiceIndex, e => e.Ignore());
3- In your controller.Post you need to map from the DTO to the entity and save the mapped entity to the database.
Now, the above solution will work for you for POST, however, you need to think about the PUT scenario and soon you will realize that you need the Ids to be included in the DTOs, and if you decided to do that then you need to revisit the mapping in point 2 and remove the Ignore code for the properties that you decided to include in the DTO.
Hope that helps.
I'm not sure where in your architecture you're using AutoMapper, but you could conceptually whitelist the properties before doing the automapping. For example, if you're in MVC and you're doing modelbinding, there are techniques (e.g. in the UpdateModel method) to include or exclude a list of properties.
Is it possible to create an object in a view and send it to a controller through ajax?
using the
$.ajax({
type: "POST", etc....
???
I want to send an object of the type that I receive in the view as
#model Project1.ViewModels.ModelSample
It's possible
This is perfectly (and easily) possible.
What about complex objects?
#xixonia provided all the information you may need to do so. But those examples are rather basic and may not provide information in case you have some sort of complex objects as:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Person Spouse { get; set; }
public IList<Person> Children { get; set; }
}
Any object that has more than a single level of properties in its tree is regarded as a complex object. Using technique provided by #xixonia will fail to work in this case.
So if you'd like to also use this kind of scenario I suggest you read this blog post that describes the whole problem in detail as well as provides a rather simple jQuery plugin that makes it possible to send even complex objects to Asp.net MVC controller actions that will be model bound to your whatever complex strong type.
Other posts on the same blog may also prove to be helpful:
successfully model bind forms to IList<T> action parameters (or within complex type parameters)
handling validation errors with Ajax requests
If you'll be using Ajax along Asp.net MVC you will find these posts very useful and will save you much of your development time when you run against such issues.
This is the way it worked for me:
$.post("/Controller/Action", $("#form").serialize(), function(json) {
// handle response
}, "json");
[HttpPost]
public ActionResult TV(MyModel id)
{
return Json(new { success = true });
}
Is it possible to create an object in
a view and send it to a controller
through ajax?
Absolutely. You can use ASP.NET MVC's model binding for this.
var data =
{
Id: 5,
Value: "Hello, world!"
};
$.post('Home/MyAction', data);
And you should have a matching POCO:
public class MyPoco
{
public int Id { get; set; }
public string Value { get; set; }
}
And an Action which takes your model to bind:
public ActionResult MyAction(MyPoco myPoco)
{
if(ModelState.IsValid)
{
// Do stuff
}
}
This should automatically deserialize your request into a POCO.