How do you manage the visible input fields accepted in an API HttpPost request? - api

In my API I have a Create method in my controller that accepts all of the models fields, but in the method I'm excluding the ID field since on a create it's generated. But in Swagger it's showing the following.
Is there a way for it not to show the following part?
"id": 0
Is a viewmodel how I should go about this?
I tried the following, but can't get it to work.
public class PartVM
{
public string Name { get; set; }
}
public interface IPartService
{
Task<Part> CreatePart(PartVM part);
Task<IEnumerable<Part>> GetParts();
Task<Part> GetPart(int partId);
}
public class PartService : IPartService
{
private readonly AppDbContext _appDbContext;
public PartService(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public async Task<Part> CreatePart(PartVM part)
{
var _part = new Part()
{
Name = part.Name
};
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
}
Here's my controller.
[Route("api/[controller]")]
[ApiController]
public class PartsController : ControllerBase
{
private readonly IPartService _partService;
public PartsController(IPartService partService)
{
_partService = partService;
}
[HttpPost]
public async Task<ActionResult<Part>> CreatePart(PartVM part)
{
try
{
if (part == null)
return BadRequest();
var _part = new Part()
{
Name = part.Name
};
var createdPart = await _partService.CreatePart(_part);
return CreatedAtAction(nameof(GetPart),
new { id = createdPart.Id}, createdPart);
}
catch (Exception /*ex*/)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error creating new record in the database");
}
}
I'm getting a build error saying "CS1503 Argument 1: cannot convert from 'MusicManager.Shared.Part' to 'MusicManager.Server.Data.ViewModels.PartVM'".
It's refering to "_part" in this line "var createdPart = await _partService.CreatePart(_part);".
Any help is appreciated, thank you!

you have a CreatePart method which receives a PartVM model, but you are sending a Part Model to it
change your method to this :
public async Task<Part> CreatePart(Part part)
{
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}

Related

Display fields (from query) on Swagger UI from complex record

I have a complex record SearchProductsRequest in a GET request that receives the parameters by query (
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc ).
app.MapGet(
$"/{ProductCatalogueApi.Version}/products",
(SearchProductsRequest request)
=> ProductApiDelegates.SearchProducts(
request));
In the record, I've implemented the bind async
public static ValueTask<SearchProductsRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter); and now the parameters from the URL automatically convert the parameters to SearchProductsRequest.
The request is working as intended, but we are using (Swashbuckle -> ) Swagger UI for development.
Swagger UI does not recognize the members from SearchProductsRequest to display them as input boxes. Is there a way to make swagger UI know them and display them so a user consulting the swagger endpoint can pass value through it?
I was hoping to get the following:
Until now, I've only managed to have the fields displayed in swagger if I have all of them in the Map.Get() explicitly.
EDIT:
Adding asked content
Record:
public record SearchProductsRequest
{
public IEnumerable<int>? Ids { get; private set; }
public string? Name { get; private set; }
public PaginationInfoRequest? PaginationInfo { get; private set; }
public SortingInfoRequest? SortingInfo { get; private set; }
public SearchProductsRequest(
IEnumerable<int>? ids,
string? name,
PaginationInfoRequest? PaginationInfo,
SortingInfoRequest? SortingInfo)
{
this.Ids = ids;
this.Name = name;
this.PaginationInfo = PaginationInfo;
this.SortingInfo = SortingInfo;
}
public static ValueTask<SearchProductsRequest?> BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
var ids = ParseIds(httpContext);
var name = httpContext?.Request.Query["name"] ?? string.Empty;
PaginationInfoRequest? pagination = null;
SortingInfoRequest? sorting = null;
if (int.TryParse(httpContext?.Request.Query["page"], out var page)
&& int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
{
pagination = new PaginationInfoRequest(page, pageItems);
}
var sortField = httpContext?.Request.Query["sortField"].ToString();
if (!string.IsNullOrEmpty(sortField))
{
sorting = new SortingInfoRequest(
sortField,
httpContext?.Request.Query["sort"].ToString() == "asc");
}
return ValueTask.FromResult<SearchProductsRequest?>(
new SearchProductsRequest(
ids,
name!,
pagination,
sorting));
}
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
private static int[]? ParseIds(HttpContext httpContext)
{
int[]? ids = null;
var commaSeparatedIds = httpContext?.Request.Query["ids"]
.ToString();
if (!string.IsNullOrEmpty(commaSeparatedIds))
{
ids = commaSeparatedIds
.Split(",")
.Select(int.Parse)
.ToArray() ?? Array.Empty<int>();
}
return ids;
}
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
}
Delegate:
internal static async Task<IResult> SearchProducts(
ILogger<ProductApiDelegates> logger,
IMapper mapper,
SearchProductsRequest request,
IValidator<SearchProductsRequest> validator,
IProductService productService)
{
using var activity = s_activitySource.StartActivity("Search products");
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.GetErrors();
logger.LogError("Bad Request: {Errors}", errors);
return Results.BadRequest();
}
try
{
logger.LogInformation("Searching product details by name");
var filtersContainer = mapper.Map<SearchProductsFiltersContainer>(request);
var products = await productService.SearchProductsAsync(filtersContainer);
if (products == null)
{
return Results.NotFound();
}
var searchProducts = BuildSearchProducts(mapper, products);
var paginationInfo = await BuildPaginationInfo(filtersContainer, productService);
var response = new SearchProductsResponse(searchProducts, paginationInfo);
return Results.Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error searching the products");
return Results.Problem();
}
}

Shopifysharp AuthorizationService.IsAuthenticRequest Returns false

Please see the following code snippet:
This is my controller:
[Produces("application/json")]
[Route("api/Shopify")]
[AllowAnonymous]
[ServiceFilter(typeof(ShopifyVerificationFilter))]
//[ApiExplorerSettings(IgnoreApi = true)]
public class ShopifyController : Controller
{
private readonly ILogger logger;
public ShopifyController(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<StoreLocatorController>();
}
[HttpGet("fetch_stock.json")]
public async Task<IActionResult> GetInventoryLevels(ShopifyFetchStock shopifyFetchStock, [FromServices] IShopifyFulfillmentServices shopifyFulfillmentServices)
{
try
{
var inventoryData = await shopifyFulfillmentServices.GetInventoryLevels(shopifyFetchStock);
return Ok(inventoryData);
}
catch (Exception ex)
{
return Ok();
}
}
}
This is my ShopifyVerificationFilter:
public class ShopifyVerificationFilter : Attribute, IAuthorizationFilter
{
private readonly IOptions<ShopifySettings> _shopifySettings;
private readonly IShopifyVerify _shopifyVerify;
public ShopifyVerificationFilter(IOptions<ShopifySettings> shopifySettings, IShopifyVerify shopifyVerify)
{
_shopifySettings = shopifySettings;
_shopifyVerify = shopifyVerify;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isVerified = _shopifyVerify.IsAuthenticShopifyRequest(context.HttpContext.Request.QueryString.Value, _shopifySettings.Value.APISecretKey);
if (!isVerified)
{
context.HttpContext.Request.EnableRewind();
isVerified = _shopifyVerify.IsAuthenticShopifyWebhook(context.HttpContext.Request.Headers, context.HttpContext.Request.Body, _shopifySettings.Value.APISecretKey, context.HttpContext.Request.QueryString.Value);
if (!isVerified)
{
context.Result = new UnauthorizedResult();
}
else
{
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
}
}
}
}
This is the implementation for the IsAuthenticShopifyRequest method:
public class ShopifyVerify : IShopifyVerify
{
public bool IsAuthenticShopifyRequest(string queryString, string APIKey)
{
var result = AuthorizationService.IsAuthenticRequest(queryString, APIKey);
return result;
}
}
When a call is made to AuthorizationService.IsAuthenticShopifyRequest(string queryString, string APIKey), it always returns false and thus not able to authenticate the shop. This piece of code was running without error before now. This issue started some couple of weeks back.
Did anything change in shopifysharp? If not please what do I need to do to get this work and if shopifysharp changed the implementation of AuthorizationService.IsAuthenticRequest(queryString, APIKey); please I need help in resolving this.
Thanks.

FluentValidation failure not returning BadRequest

I have wired up FluentValidation as per instructions, and when debuging test I can see that model is invalid based on the test setup, but exception is not thrown, but rather method on the controller is being executed. This is on 3.1 with EndPoint routing enabled. Is there anything else one needs to do to get this to work and throw. What happens is that validation obviously runs; it shows as ModelState invalid and correct InstallmentId is invalid, but it keeps processing in Controller instead of throwing exception.
services.AddMvc(
options =>
{
options.EnableEndpointRouting = true;
//// options.Filters.Add<ExceptionFilter>();
//// options.Filters.Add<CustomerRequestFilter>();
})
.AddFluentValidation(
config =>
{
config.RegisterValidatorsFromAssemblyContaining<Startup>();
})
Command and Validator
public class ProcessManualPayment
{
public class Command
: CustomerRequest<Result?>
{
public Guid PaymentPlanId { get; set; }
public Guid InstallmentId { get; set; }
public Guid PaymentCardId { get; set; }
}
public class Validator : AbstractValidator<Command>
{
public Validator()
{
this.RuleFor(x => x.CustomerId)
.IsValidGuid();
this.RuleFor(x => x.PaymentPlanId)
.IsValidGuid();
this.RuleFor(x => x.InstallmentId)
.IsValidGuid();
this.RuleFor(x => x.PaymentCardId)
.IsValidGuid();
}
}
Controller
[Authorize]
[HttpPost]
[Route("payments")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ProcessManualPayment(
[FromBody]
ProcessManualPayment.Command command)
{
Test
[Fact]
public async Task When_Command_Has_Invalid_Payload_Should_Fail()
{
var client = this.factory.CreateClient();
// Arrange
var validCmd = new ProcessManualPayment.Command()
{
CustomerId = Guid.NewGuid(),
PaymentPlanId = Guid.NewGuid(),
InstallmentId = Guid.NewGuid(),
PaymentCardId = Guid.NewGuid(),
};
var validCmdJson = JsonConvert.SerializeObject(validCmd, Formatting.None);
var jObject = JObject.Parse(validCmdJson);
jObject["installmentId"] = "asdf";
var payload = jObject.ToString(Formatting.None);
// Act
var content = new StringContent(payload, Encoding.UTF8, MediaTypeNames.Application.Json);
var response = await client.PostAsync(MakePaymentUrl, content);
var returned = await response.Content.ReadAsStringAsync();
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
[Fact]
public async Task When_Payload_Is_Null_Should_Fail()
{
// Arrange
var client = this.factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(MakePaymentUrl, null);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
GuidValidator
public class GuidValidator : PropertyValidator
{
public GuidValidator()
: base("'{PropertyName}' value {AttemptedValue} is not a valid Guid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
context.MessageFormatter.AppendArgument("AttemptedValue", context.PropertyValue ?? "'null'");
if (context.PropertyValue == null)
{
return false;
}
Guid.TryParse(context.PropertyValue.ToString(), out var value);
return IsValid(value);
}
private static bool IsValid(Guid? value) =>
value.HasValue
&& !value.Equals(Guid.Empty);
}
Mystery solved, I was missing [ApiController] attribute on the controller.

.NET Core 3 Service result to Controller to FE (data or error reason)

building new app on .net core 3 and Angular. Overall all works, but I want to add more intelligence to service/controller part. This is one of the api's, but this logic can be applied to others as as well.
Here's my Login Controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLoginDto userLogin)
{
var token = await _userService.LoginAsync(userLogin);
if (token != null)
{
return Ok(token);
}
else
{
return BadRequest("Something went wrong");
}
}
And here's my userService:
public async Task<string> LoginAsync(UserLoginDto userLogin)
{
var user = await _userManager.FindByEmailAsync(userLogin.Email);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, userLogin.Password, false, true);
if (result.Succeeded)
{
var roles = await _userManager.GetRolesAsync(user);
var tokenJson = _jwtManager.getJwtToken(user.Email, roles);
return tokenJson;
}
else
{
return null; // Return BadRequest and result reason (Failed, lockedout, etc)
}
}
else
{
return null; // User not found, return NotFound }
}
Here's my question - how should I return result from userService to Controller so, that I could respond to API call either with Ok(token) or BadRequest/NotFound with the reason.
If I keep all this LoginAsync code in controller, then it's easy, but I want to use service.
One option I was thinking was to introduce new class, something like:
public class BaseResult
{
public object Data { get; set; }
public long ResponseCode { get; set; }
public string ErrorMessage { get; set; }
}
then always return this class from service, but not fully like that idea either.
thanks!
Here is a working demo you could follow:
Model:
public class UserLoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
IUserService:
public interface IUserService
{
Task<IActionResult> LoginAsync(UserLoginDto userLogin);
}
UserService:
public class UserService: IUserService
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IJwtManager _jwtManager;
public UserService(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IJwtManager jwtManager)
{
_userManager = userManager;
_signInManager = signInManager;
_jwtManager = jwtManager;
}
public async Task<IActionResult> LoginAsync(UserLoginDto userLogin)
{
var user = await _userManager.FindByEmailAsync(userLogin.Email);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, userLogin.Password, false, true);
if (result.Succeeded)
{
var roles = await _userManager.GetRolesAsync(user);
var tokenJson = _jwtManager.getJwtToken(user.Email, roles);
return new OkObjectResult(tokenJson);
}
else
{
// Return BadRequest and result reason (Failed, lockedout, etc)
if (result.IsNotAllowed)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
// Email isn't confirmed.
return new BadRequestObjectResult("Email isn't confirmed.");
}
if (!await _userManager.IsPhoneNumberConfirmedAsync(user))
{
// Phone Number isn't confirmed.
return new BadRequestObjectResult("Phone Number isn't confirmed.");
}
return new BadRequestObjectResult("Login IsNotAllowed");
}
else if (result.IsLockedOut)
{
// Account is locked out.
return new BadRequestObjectResult("Account is locked out.");
}
else if (result.RequiresTwoFactor)
{
// 2FA required.
return new BadRequestObjectResult("2FA required");
}
else
{
// Password is incorrect.
return new BadRequestObjectResult("Password is incorrect.");
}
}
}
else
{
return new NotFoundObjectResult("Username is incorrect"); // User not found, return NotFound }
}
}
}
Controller:
public class HomeController : Controller
{
private readonly IUserService _userService;
public HomeController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLoginDto userLogin)
{
var result= await _userService.LoginAsync(userLogin);
return result;
}
}
Startup.cs:
Not sure what is _jwtManager.getJwtToken in your code,so I just guess it is an interface and owns a JwtManager class implemented this interface.And it contains a getJwtToken method which generated the token.
services.AddScoped<IUserService, UserService>();
services.AddScoped<IJwtManager, JwtManager>();

Restore AJAX handling for ASP.NET Core to previous functionality

In previous MVC5 and below, you could make an ajax call that unwrapped the parameters properly:
JS:
$.post('/controller/endpoint',{intparam: 1, strparam: 'hello'})
CS:
public ActionResult endpoint(int intparam, string strparam){}
In the new aspnetcore, it has changed:
CS:
public CustomClassWrapper{
public int intparam {get;set;}
public string stringparam {get;set;}
}
public ActionResult endpoint([FromBody]CustomClassWrapper item){}
To sum it up, in the new framework, you need to write a wrapper class and can only pass one [FromBody] parameter to the method. Previously, the params would be unwrapped by variable name correctly.
So, i'm trying to re-implement this functionality in an aspnetcore middleware component. I'm having difficulty in how to accomplish calling the controller method properly with the parameters.
My current cut-down code:
public async Task Invoke(HttpContext context)
{
if (IsAjaxRequest(context.Request))
{
try
{
string bodyContent = new StreamReader(context.Request.Body).ReadToEnd();
var parameters = JsonConvert.DeserializeObject(bodyContent);
///What to do here?
}
catch (Exception ex)
{
throw new Exception("AJAX method not found ", ex);
}
}
else
{
await _next(context);
}
}
I'm really just not sure about what to do after deserializing the parameters. I have the URL for the endpoint and also the params correctly. Just need to know how to call the method and return the result as JSON. Should i be using Reflection to get the controller method? Or is there a better way using MVC?
Try implement custom IModelBinder.
public class BodyFieldModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
bindingContext.HttpContext.Request.EnableRewind(); // required to read request body multiple times
var inputStream = bindingContext.HttpContext.Request.Body;
if (inputStream.Position != 0L)
inputStream.Position = 0;
var bodyValue = new StreamReader(inputStream, Encoding.UTF8).ReadToEnd();
var jsonObject = (JObject)JsonConvert.DeserializeObject<object>(bodyValue);
if (jsonObject.TryGetValue(bindingContext.FieldName, out var jToken))
{
var jsonSerializer = JsonSerializer.Create();
var result = jToken.ToObject(bindingContext.ModelType, jsonSerializer);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}
Be careful, the code above lacks error handling and etc.
And use it like this:
[HttpPost]
public IActionResult Endpoint([ModelBinder(typeof(BodyFieldModelBinder))] int intparam)
Also you could implement custom attribute to reduce complexity of declaration:
public class BodyFieldAttribute : ModelBinderAttribute
{
public BodyFieldAttribute()
: base(typeof(BodyFieldModelBinder))
{
}
}
it's very simple thing i don't know why it not working at your end
JS
$.post('actionMethodURl', { FirstName: '1', LastName: 'hello' }).done(Successfunction);
CS
[HttpPost]
public ActionResult endpoint(string FirstName,string LastName)
{
object Message = string.Empty;
if (ModelState.IsValid)
{
Message = "Pass";
}
else
{
Message = ModelState.Errors();
}
return Json(Message);
}