I really Didn't get proper Title for this question. Please correct it if its misleading.
I have a WebApi controller, where there are multiple validation check are there. Controller sample code
public async Task<IActionResult> UploadFile(IFormFile file)
{
try
{
return file.IsValid();
//some more Functionality
}
}
Here Isvalid is a Extension method where the code is as follows
public static IActionResult PrepareResult(this ControllerBase controller, IFormFile file)
{
if (file== null)
{
return controller.Badrequest("No data sent");
}
return controller.Ok();
}
Issue:- In current scenario , if the file is Null then Extension method will be returning Badrequest() & the same will be returned to the client. But if file is not null then It's going to return Ok() & same will be returned to the Clint, where as i have more code to execute(i.e.//some more Functionality).
I don't want to return controller.Ok(), so that for positive scenario i can continue with my remaining code.
NB:- i don't want to assign to any variable & check with If condition. In order to avoid if condition only i am using extension methods.
Not sure why you don't want to assign varaibles and avoid if condition as this is the most efficient way. You can use exception handling, though that comes with a performance cost.
public static void EnsureFileIsValid(this IFormFile file)
{
if(file == null) { throw new InvalidOperationException("No data sent"); }
}
public async Task<IActionResult> UploadFile(IFormFile file)
{
try
{
file.EnsureFileIsValid();
return Ok();
}
catch(InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
You can pass a action to your method like :
public static IActionResult PrepareResult(this ControllerBase controller, IFormFile file, Action<IFormFile> work)
{
if (file == null)
{
return controller.Badrequest("No data sent");
}
work(file);
return controller.Ok();
}
In you action, the use is :
public class FilesController : ControllerBase
{
[HttpPost]
public IActionResult UploadFile([FromServices]IFileService fileService, IFormFile file)
{
return Ok(PrepareResult(file, fileService.Upload));
}
}
But maybe you can consider to use validation.
In validation step, you enforce a parameter isn't null with RequiredAttribute.
ApiControllerAttribute enforce the validation before the action is called. If the validation fail, then ASP.NET Core return directly BadRequest and the action isn't called.
In this example, if the parameter file is null then the action isn't called and it return BadRequest :
[ApiController]
public class FilesController : ControllerBase
{
[HttpPost]
public IActionResult UploadFile([Required]IFormFile file)
{
//Do something
return Ok();
}
[HttpPut]
public IActionResult UploadFileBis([Required] IFormFile file)
{
//Do something
return Ok();
}
}
PS : You can use [ApiControllerAttribute] at assembly level, it will be enable to all controllers in assembly :
[assembly: ApiController]
If you want to verify conditions and define the error response in the same lower layer class, here is a solution.
First, let's create a custom exception that will hold the http status code and message to return:
// serialization implementation redacted
public class InfrastructureException : Exception
{
public HttpStatusCode HttpStatusCode { get; }
public InfrastructureException(HttpStatusCode code, string message) : base(message)
{
HttpStatusCode = code;
}
}
We need a class to handle the response serialization:
public class ExceptionResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then create a middleware that handle exceptions:
public class InfrastructureExceptionMiddleware
{
private readonly RequestDelegate next;
public InfrastructureExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IHostEnvironment hostEnvironment)
{
try
{
await this.next(httpContext);
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
ExceptionResponse response = exception is InfrastructureException infrastructureException
? new ExceptionResponse()
{
StatusCode = (int)infrastructureException.HttpStatusCode,
Message = infrastructureException.Message
}
: new ExceptionResponse()
{
StatusCode = (int)HttpStatusCode.InternalServerError,
Message = ReasonPhrases.GetReasonPhrase(context.Response.StatusCode)
};
return context.Response.WriteAsync(response.ToString());
}
}
Now, we need to register our middleware:
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// redacted
app.UseMiddleware<InfrastructureExceptionMiddleware>();
// redacted
}
}
In the controller, we delegate the validation to the extension method:
public async Task<IActionResult> UploadFile(IFormFile file)
{
file.IsValid();
// now you can consider file is valid
}
Finally, in the extension method, we throw the exception:
public static void IsValid(this IFormFile file)
{
if(file == null)
{
throw new InfrastructureException(HttpStatusCode.BadRequest, "No data sent");
}
if(...) // condition for Http NotFound
{
throw new InfrastructureException(HttpStatusCode.NotFound, "Data not found");
}
// other validation conditions
}
Related
I am using c# core 3.1
I have several endpoints with a structure similar to this:
[HttpPost]
public async Task<ActionResult<Usuario>> Post([FromBody] User user)
{
context.User.Add(user);
try
{
await context.SaveChangesAsync();
}
catch (Exception ex)
{
...
}
return Ok();
}
The user sends an object like this:
{"rol": "Administrator", "name":"pedro"}
I would like to validate that if it contains a certain value, allow to continue with the endpoint logic or otherwise do not allow it.
for example I want to make a verification that if rol= Administrator allows to continue with my endpoint.
I am very confused but I don't know if something like this exists but it works as a middleware where can I get the data that is sent from the client side to perform validations:
[HttpPost]
[MyCustomMiddleWare]
.
.
public class MyCustomMiddleWare
{
.
.
if (dataFromClientSide.rol== "Administrator")
{
continue
}
else{
return Ok(new { message:"Not has permission" })
}
}
It looks like you just need some modelvalidation like bellow:
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
[ApiController]
[Route("api/[controller]")]
public class YourController : ControllerBase
{
public class User
{
[RegularExpression(pattern: "Administrator", ErrorMessage = "Your error message.")]
public string Role { get; set; }
public string Name { get; set; }
}
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] User user)
{
if (this.ModelState.IsValid)
{
return this.ValidationProblem();
}
// Do something here;
return this.Ok();
}
}
But if you insist to do it with middleware it will looks like this bellow:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<UserRoleMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
public class UserRoleMiddleware
{
private readonly RequestDelegate next;
public UserRoleMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
using var reader = new StreamReader(httpContext.Request.Body);
var body = await reader.ReadToEndAsync();
var user = JsonConvert.DeserializeObject<User>(body);
if (user != null && !user.Role.Equals("Administrator", StringComparison.OrdinalIgnoreCase))
{
// Redirect or do somethings else.
}
await next(httpContext);
}
}
To validate specific endpoints just implement ActionFilterAttribute:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<AdministratorAttribute>();
}
public class AdministratorAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionArguments.TryGetValue("user", out object user))
{
if (!(user as User).Role.Equals("Administrator"))
{
// Redirect or something
}
}
base.OnActionExecuting(context);
}
}
[HttpPost]
[Administrator]
public async Task<IActionResult> PostAsync([FromBody] User user)
{
return this.Ok();
}
I am sending an object from my web project to my API project within the same solution. I have a class that uses RestSharp and acts as the sender to the API for all services.
Currently, there is one API controller that is receiving the object, but not all of the parameters are being retained and show up with null values via PUT. However, a different controller using the ApiClient's 'PutAsync' receives its own object with all the values intact.
I've even tried changing the method to receive as a POST, but still no success.
Am I missing something, or is there something wrong that is happening with the serialization/de-serialization of the object?
public class UserInfo
{
public int UserId { get; set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string EmailAddress { get; private set; }
}
internal class WebService : IWebService
{
public async Task<bool> UpdateProfile(UserInfo userInfo)
{
try
{
var url = "/User/UpdateProfile";
return await apiClient.PutAsync<UserInfo, bool>(url, userInfo);
}
catch (Exception ex)
{
this.logger.LogError("Error in UpdateProfile", ex);
throw;
}
}
}
RestSharp Setup
internal class ApiClient : IApiClient
{
public async Task<TOut> PutAsync<TIn, TOut>(string url, TIn data) where TIn : new()
{
return await PushData<TIn, TOut>(url, data, Method.PUT);
}
private async Task<TOut> PushData<TIn, TOut>(string url, TIn data, Method method) where TIn : new()
{
var request = new RestRequest(url, method);
request.AddHeader(HttpRequestHeader.Authorization.ToString(), $"Bearer {GetApiAccessToken()}");
request.AddJsonBody(data);
var result = await client.ExecuteAsync<TOut>(request);
if (result.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Unable to push API data");
}
return result.Data;
}
}
Data in the request prior to being sent out, found under the Parameters, are as followed:
Data sent to API UserController
[Produces("application/json")]
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpPut]
[Route("UpdateProfile")]
public async Task<IActionResult> UpdateProfile([FromBody] ProfileUpdateInfo profileInfo)
{
try
{
var status = await this.service.UpdateProfile(profileInfo);
return Ok(status);
}
catch (Exception ex)
{
return BadRequest("Error in updating profile");
}
}
}
This is the Data that shows up in the parameter:
Data Consumed by API UserController
After reviewing my code again, the problem was that I had the set accessor to private on all string properties, which is why the int property was still coming through.
I am working on asp.net core version 2.1, I have created a sample API project which works fine but I am unable to modify the status code with a custom message for example:
In Postman:
200 OK
Expecting:
200 Custom_Message
The code that I tried:
[HttpGet]
public IActionResult Get()
{
Response.StatusCode = 200;
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "Custom_Message";
return Ok();
}
Postman's current Output:
GitHub Repository
I think you should create your custom class:
public class CustomResult : OkResult
{
private readonly string Reason;
public CustomResult(string reason) : base()
{
Reason = reason;
}
public override void ExecuteResult(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.HttpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = Reason;
context.HttpContext.Response.StatusCode = StatusCode;
}
}
Then in your controller method:
[HttpGet]
public IActionResult Get()
{
return new CustomResult("Custom reason phrase");
}
Output
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class BaseApiController : ControllerBase
{
public override OkResult Ok()
{
return base.Ok();
}
public override OkObjectResult Ok([ActionResultObjectValue] object value)
{
return base.Ok(new Response
{
Result = value,
Status = ResultStatus.Success,
Message = string.Empty
});
}
}
In our application we are using mediatr and there is a common pattern as follows:
class SomeController
{
public Action Foo(SomeRequest request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
This code repeats for every API end point, regardless of the HTTP method.
I read about API conventions but I guess that is about Swagger, API analyser and such.
How can I avoid having this repetetive code above?
class BaseController
{
protected static IActionResult GenericAction(object request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
Then
class SomeController : BaseController
{
public Action Foo(SomeRequest request)
{
return GenericAction(request);
}
}
if your methods always has same structure you can even generalize it more
class BaseController<TRequest>
{
public virtual Action Foo(SomeRequest request)
{
return GenericAction(request);
}
protected static IActionResult GenericAction(TRequest request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
Then
class SomeController<SomeRequest> : BaseController
{
}
Hi I have table employee with some fields
to validate fields I have created two layers
Service layer
Employee repository
Employee repository code is
namespace MvcApplication2.Models
{
public interface IEmployeeMainTableRepository
{
bool CreateEmployee(EMP_MAIN_TBL EmployeeToCreate);
IEnumerable<EMP_MAIN_TBL> ListEmployees();
}
public class EmployeeRepository : MvcApplication2.Models.IEmployeeMainTableRepository
{
private EMPLOYEE_SYSTEMEntities _entities = new EMPLOYEE_SYSTEMEntities();
public IEnumerable<EMP_MAIN_TBL> ListEmployees()
{
return _entities.EMP_MAIN_TBL.ToList();
}
public bool CreateEmployee(EMP_MAIN_TBL EmployeeToCreate)
{
try
{
// _entities.AddToEMP_MAIN_TBL(productToCreate);
_entities.SaveChanges();
return true;
}
catch
{
return false;
}
}
}
And service layer contains
public interface IEmployeeService
{
bool CreateEmployee(EMP_MAIN_TBL EmployeeToCreate);
System.Collections.Generic.IEnumerable<EMP_MAIN_TBL> ListEmployees();
}
public class EmployeeService : MvcApplication2.Models.IEmployeeService
{
private IValidationDictionary _validatonDictionary;
private IEmployeeMainTableRepository _repository;
public EmployeeService(IValidationDictionary validationDictionary, IEmployeeMainTableRepository repository)
{
_validatonDictionary = validationDictionary;
_repository = repository;
}
protected bool ValidateEmployee(EMP_MAIN_TBL employeeToValidate)
{
if (employeeToValidate.EMP_NM == null)
_validatonDictionary.AddError("EMP_NM", "Name is required.");
if (employeeToValidate.PLCE_OF_BRTH == null)
_validatonDictionary.AddError("PLCE_OF_BRTH", "Place of birth is required.");
return _validatonDictionary.IsValid;
}
public IEnumerable<EMP_MAIN_TBL> ListEmployees()
{
return _repository.ListEmployees();
}
public bool CreateEmployee(EMP_MAIN_TBL EmployeeToCreate)
{
// Validation logic
if (!ValidateEmployee(EmployeeToCreate))
return false;
// Database logic
try
{
_repository.CreateEmployee(EmployeeToCreate);
}
catch
{
return false;
}
return true;
}
and I have created two more classes to add validation messages
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
And
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
#endregion
}
finally employee controllers contains below structure
public class EmployeeController : Controller
{
private IEmployeeService _service;
public EmployeeController()
{
_service = new EmployeeService(new ModelStateWrapper(this.ModelState), new EmployeeRepository());
}
public EmployeeController(IEmployeeService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListEmployees());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View(new EMP_MAIN_TBL());
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
[HttpPost]
public ActionResult Create([Bind(Exclude = "EMP_ID")] EMP_MAIN_TBL employeeToCreate)
{
if (!_service.CreateEmployee(employeeToCreate))
return View();
return RedirectToAction("Index");
}
}
}
and my view looks like this
My question is above code working fine for server side validation
but how do I achieve validation on client side using above same code
please
Since you are already validating on the service side you could return the ModelStateDictionary instead of the bool, you could then check that it is valid on the client side.
But this won't help when it comes to checking that the whole service method has finished, so you could create a new type that returns say a bool and the ModelStateDictionary.
Another approach is to use Fault Exceptions. You can create your own fault exception that would get thrown when the model state is not valid. This Model State Fault could contain your ModelStateDictionary.
So from that you have three options.
Change the return type to the ModelStateDictionary.
Create a new return type to return a result and a ModelStateDictionary.
Use Fault Exceptions that occur when the Model State isn't valid.
Personally I would use the third approach, as you can then still use your original return type, and then just need to catch the Fault like you would an Exception. Here is an example and also MSDN