Can you use ModelBinding with a GET request? - c#

I'm curious if it's possible to bind a query string that is passed in with a GET request to a Model.
For example, if the GET url was https://localhost:1234/Users/Get?age=30&status=created
Would it be possible on the GET action to bind the query parameters to a Model like the following:
[HttpGet]
public async Task<JsonResult> Get(UserFilter filter)
{
var age = filter.age;
var status = filter.status;
}
public class UserFilter
{
public int age { get; set; }
public string status { get; set; }
}
I am currently using ASP.NET MVC and I have done quite a bit of searching but the only things I can find are related to ASP.NET Web API. They suggest using the [FromUri] attribute but that is not available in MVC.

I just tested the this, and it does work (at least in .net core 3.1)
[HttpGet("test")]
public IActionResult TestException([FromQuery]Test test)
{
return Ok();
}
public class Test
{
public int Id { get; set; }
public string Yes { get; set; }
}

You can can create an ActionFilterAttribute where you will parse the query parameters, and bind them to a model. And then you can decorate your controller method with that attribute.
For example
public class UserFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as CustomApiController;
var queryParams = actionContext.Request.GetQueryNameValuePairs();
var ageParam = queryParams.SingleOrDefault(a => a.Key == "age");
var statusParam = queryParams.SingleOrDefault(a => a.Key == "status");
controller.UserFilter = new UserFilter {
Age = int.Parse(ageParam.Value),
Status = statusParam.Value
};
}
}
The CustomApiController (inherits from your current controller) and has a UserFilter property so you can keep the value there. You can also add null checks in case some of the query parameters are not sent with the request..
Finally you can decorate your controller method
[HttpGet]
[UserFilter]
public async Task<JsonResult> Get()
{
var age = UserFilter.age;
var status = UserFilter.status;
}

Related

SoftDelete : System.Collections.Generic.List<##.##.Employee>' to 'Microsoft.AspNetCore.Mvc.IActionResult'

I got in error when I try to perform Sofdelete
Cannot implicitly convert type
'System.Collections.Generic.List<##.##.Employee>' to
'Microsoft.AspNetCore.Mvc.IActionResult'.
Here is my index I tried to use ToList() and ToList<Employee>, but it's not working
public IActionResult Index()
{
var employees = _dbContext.Employees.Where(x => x.status == '1')
.ToList();
return employees;
}
My DbContext:
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach( var entry in ChangeTracker.Entries())
{
var entity = entry.Entity;
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entity.GetType().GetProperty("status").SetValue(entity, '0');
}
}
return base.SaveChanges();
}
}
Employee:
namespace empAPI.Models
{
public class Employee
{
public Guid Id { get; set; }
public char status{ get; set; } = '1';
public string Name { get; set; }
public string Department { get; set; }
public DateTime? CreatedDate { get; set; } = DateTime.Now;
}
}
Change your code to:
public IActionResult Index()
{
var employees = _dbContext.Employees.Where(x => x.status == '1').ToList();
return View(employees);
}
Read the following article: Understanding Action Results
A controller action returns something called an action result. An
action result is what a controller action returns in response to a
browser request.
The ASP.NET MVC framework supports several types of action results
including:
ViewResult - Represents HTML and markup.
EmptyResult - Represents no result.
RedirectResult - Represents a redirection to a new URL.
JsonResult - Represents a JavaScript Object Notation result that can be used in an AJAX application.
JavaScriptResult - Represents a JavaScript script.
ContentResult - Represents a text result.
FileContentResult - Represents a downloadable file (with the binary content).
FilePathResult - Represents a downloadable file (with a path).
FileStreamResult - Represents a downloadable file (with a file stream).
All of these action results inherit from the base ActionResult class.
In most cases, a controller action returns a ViewResult.

How to build server url for DTOs

I want to add a url to my DTOs, specifically a canonical link for each object in a collection. I'm using Dapper to map my SQL result into a collection, so I was hoping to do this within the POCO itself.
public class Group {
public string Name { get; set; }
public string url {
get {
//CODE TO BUILD LINK HERE (e.g., https://example.com/v1/groups/1234)
}
}
}
I've seen use of Url.Link() but I've only gotten that to work within my controller - not my url property above. If it can't be done within the POCO, is there a preferred way to update my collection of Group objects after Dapper loads them?
After dapper/service loads the records, you will then need to traverse the collection and generate the URL based on the identifier for the record and the route
Assuming
public class Group {
public string Name { get; set; }
public string Url { get; set; }
}
A controller that generates the record URL could follow the following pattern
[Route("v1/groups")]
public class GroupsController : Controller {
//...omitted for brevity
//GET v1/groups
[HttpGet("")]
public IActionResult Get() {
IEnumerable<Group> groups = service.GetGroups()
.Select(group => {
var url = Url.RouteUrl("GetGroup", new { name = group.Name });
group.Url = url;
return group;
}).ToList();
if(!groups.Any()) return NotFound();
return Ok(groups);
}
//GET v1/groups/1234
[HttpGet("{name}", Name = "GetGroup")]
public IActionResult Get(string name) {
var group = service.GetGroup(name);
if(group == null) return NotFound();
group.Url = Url.RouteUrl("GetGroup", new { name = group.Name });
return Ok(group);
}
}
Reference Generating URLs by route

Asp net core bad model in parameter at post action is not set to null

I have this problem I'm following the Api course on pluralsight and I've been trying to understand why when I pass an invalid Dto in a post request it doesn't get set to null.
Here is my Dto
public class AuthorCreateDto
{
public string Name { get; set; }
public string LastName { get; set; }
public int GenreId { get; set; }
public int CountryId { get; set; }
}
and action
[Route("")]
[HttpPost]
public ActionResult<AuthorDto> CreateAuthor([FromBody]AuthorCreateDto authorCreateDto)
{
if (authorCreateDto == null)
return BadRequest();
var author = Mapper.Map<Author>(authorCreateDto);
if (TryValidateModel(author))
return BadRequest();
var newAuthor = _authorService.CreateAuthor(author);
var newAuthorDto = Mapper.Map<AuthorDto>(newAuthor);
return CreatedAtRoute("GetAuthor", new { id = newAuthor.Id }, newAuthorDto);
}
so when I post an invalid json as
{
"epa": 2,
"wdawd": "awdawd"
}
authorCreateDto does not get set to null while on the course it does. Idk whats going on thank you
For Asp.Net Core, its built-in serializer is Newtonsoft.Json and for FromBody, it will use JsonInputFormatter to bind the request body to model.
By default, SerializerSettings.MissingMemberHandling is Ignore which return default value for the properties which is missing in the request body.
If you prefer null for authorCreateDto, you could configure it with Error by
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error;
});
}

Asp.Net Core 2.1 ApiController does not automatically validate model under unit test

I am using xUnit to unit test an ASP.NET Core 2.1 controller method that returns an ActionResult<T>. The controller class is decorated with the [ApiController] attribute so that the method, when running live, performs model validation automatically. However, the controller method does not automatically fire model validation in my unit test, and I can't figure out why.
Here is my Unit Test.
[Fact]
public async Task post_returns_400_when_model_is_invalid()
{
// arrange
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
var mockHttpMessageHandler = new Mock<FakeHttpMessageHandler> {CallBase = true};
mockHttpMessageHandler.Setup(mock => mock.Send(It.IsAny<HttpRequestMessage>()))
.Returns(new HttpResponseMessage(HttpStatusCode.Accepted)
{
Content = new StringContent("{\"response\": {\"numFound\": 0, \"start\": 0, \"docs\": []}}")
});
var httpClient =
new HttpClient(mockHttpMessageHandler.Object)
{
BaseAddress = new Uri("http://localhost:8983/"),
DefaultRequestHeaders = {{"Accept", "application/json"}}
};
mockHttpClientFactory.Setup(mock => mock.CreateClient("SolrApi")).Returns(httpClient);
var slackOptions = Options.Create<SlackOptions>(new SlackOptions());
var prdSolrService = new PrdSolrService(Options.Create<PrdOptions>(new PrdOptions()));
var slackMessageService = new PrdSlackMessageService(new HtmlTransformService(), slackOptions);
var controller = new SlackPrdController(slackOptions, mockHttpClientFactory.Object, prdSolrService, slackMessageService);
var slackRequest = new SlackRequest();
// act
var sut = await controller.Post(slackRequest);
// assert
// note: validation should fail and not return a SlackMessage, but validation is not firing
// auto-validation is part of [ApiController] attribute on the Controller class
Assert.IsType<BadRequestObjectResult>(sut.Result);
}
Here is the Controller method being tested.
[HttpPost]
public async Task<ActionResult<SlackMessage>> Post(SlackRequest request)
{
if (request.Token != _slackOptions.Token)
{
ModelState.AddModelError("Token", "Invalid verification token.");
return BadRequest(ModelState);
}
var solrUri = _solrService.SlackRequestToSolrUri(request);
var client = _httpClientFactory.CreateClient("SolrApi");
var raw = await client.GetStringAsync(solrUri);
var response = _solrService.ParseSolrResponse(raw);
var slackMessage = _slackMessageService.InitialMessage(response);
return slackMessage;
}
Here is the SlackRequest model, where the Token property is required.
public class SlackRequest
{
public SlackRequest() {}
[JsonProperty("token")]
[Required]
public string Token { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
[JsonProperty("team_domain")]
public string TeamDomain { get;set; }
[JsonProperty("enterprise_id")]
public string EnterpriseId { get; set; }
[JsonProperty("enterprise_name")]
public string EnterpriseName { get; set; }
[JsonProperty("channel_id")]
public string ChannelId { get; set; }
[JsonProperty("channel_name")]
public string ChannelName { get; set; }
[JsonProperty("user_id")]
public string UserId { get; set; }
[JsonProperty("user_name")]
public string UserName { get; set; }
[JsonProperty("command")]
public string Command { get;set; }
[JsonProperty("text")]
public string Text { get; set; }
[Url]
[JsonProperty("response_url")]
public string ResponseUrl { get; set; }
[JsonProperty("trigger_id")]
public string TriggerId { get; set; }
}
The controller class is decorated with the [ApiController] attribute so that the method, when run live, performs model validation automatically. However, the controller method does not automatically fire model validation in my unit test, and I can't figure out why.
The ApiControllerAttribute is metadata that is only relevant at run time by the infrastructure, so that means you will have to use TestServer in an integration test and actually request the action under test for it to be part of the test and recognized by the framework.
This is because as the request goes through the infrastructure, the attribute basically tags the controller/action to let an action filter (ModelStateInvalidFilter) inspect the model and update the model state of the request. If model is found to be invalid it will short circuit the request so that is does not even reach the action to be invoked.

How to map users identity and Auditable properties in view model

This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.

Categories