Web API 2 return custom success / error object - asp.net-web-api

I am having a bit of issues with my Web API 2 project.
In order to connect to a mobile app client i will need to provide with custom success / error object in this way:
Products (GET)
on Success 200: return a list with (ID, Name)
on Error: return a custom object with (ErrorCode, ErrorDescription)
How can I do this in a nice way?
Using JsonResult or is there a better way?

I would do it like this:
public class CustomErrorObject
{
public string ErrorCode { get; set; }
public string ErrorDescription { get; set; }
}
public class HandleApiExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
HttpRequestMessage request = actionExecutedContext.ActionContext.Request;
CustomErrorObject response = new CustomErrorObject();
response.ErrorCode = actionExecutedContext.Exception.Data("Text");
response.ErrorDescription = actionExecutedContext.Exception.Data("Detail");
actionExecutedContext.Response = request.CreateResponse(HttpStatusCode.BadRequest, response);
}
}
Then in the Global.asax add this line to the Application_Start event:
GlobalConfiguration.Configuration.Filters.Add(new HandleApiExceptionAttribute())
If you'd like to know more about exception handling in Web API: here

Related

Conditional validation based on request route with asp.net core 2.2 and FluentValidation

So Basically i wrote a validator for my class with FluentValidation and also a filter to do the validation task for me in my webAPI project, so far it's OK but assume that my User class has firstname,lastname,email,password properties
and i have two routes (one for register and the other one for login)
and as you might have noticed required properties are different on these route.
Thus,should I really need to write individual validation for each and every action i have?because this makes a lot of code code duplication and it's hard to change.is there any way to just add required condition based on the request coming with single validation class?
Any suggestion???
A better practice would be to use a factory pattern for your validations and use a an action filter to short circuit bad requests. You could validate any action argument(Headers, Request Bodies, etc..) with something like this.
public class TestValidationAttribute : Attribute, IActionFilter
{
private string _requestModelName;
public TestValidationAttribute(string requestModelName)
{
_requestModelName = requestModelName;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// using Microsoft.Extensions.DependencyInjection;
var services = context.HttpContext.RequestServices;
var accessor = services.GetService<IHttpContextAccessor>();
var factory = services.GetService<ITestValidatorFactory>();
var tokens = accessor.HttpContext.GetRouteData().DataTokens;
if (!tokens.TryGetValue("RouteName", out var routeNameObj))
{
throw new Exception($"Action doesn't have a named route.");
}
var routeName = routeNameObj.ToString();
var validator = factory.Create(routeName);
if (!context.ActionArguments.TryGetValue(_requestModelName, out var model))
{
throw new Exception($"Action doesn't have argument named {_requestModelName}.");
}
TestModel test;
try
{
test = (TestModel) model;
}
catch (InvalidCastException)
{
throw new Exception($"Action argument can't be casted to {nameof(TestModel)}.");
}
var validation = validator.Validate(test);
if (!validation.Successful)
{
context.Result = new BadRequestObjectResult(validation.ResponseModel);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
public class TestController : Controller
{
[HttpPost]
[Route("Test/{id}", Name = "TestGet")]
[TestValidation("model")]
public IActionResult Test(TestModel model)
{
return Ok();
}
}
public class ValidationResult
{
public bool Successful { get; }
public ResponseModel ResponseModel { get; }
}
public class TestModel
{
}
public interface ITestValidator
{
ValidationResult Validate(TestModel model);
}
public interface ITestValidatorFactory
{
ITestValidator Create(string routeName);
}

Mediator Api call failing

I'm trying to make a simple request using mediator and .net core. I'm getting an error that I am not understanding. All I'm trying to do is a simple call to get back a guid.
BaseController:
[Route("api/[controller]/[action]")]
[ApiController]
public class BaseController : Controller
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ?? (_mediator = HttpContext.RequestServices.GetService<IMediator>());
}
Controller:
// GET: api/Customer/username/password
[HttpGet("{username}/{password}", Name = "Get")]
public async Task<ActionResult<CustomerViewModel>> Login(string username, string password)
{
return Ok(await Mediator.Send(new LoginCustomerQuery { Username = username,Password = password }));
}
Query:
public class LoginCustomerQuery : IRequest<CustomerViewModel>
{
public string Username { get; set; }
public string Password { get; set; }
}
View Model:
public class CustomerViewModel
{
public Guid ExternalId { get; set; }
}
Handler:
public async Task<CustomerViewModel> Handle(LoginCustomerQuery request, CancellationToken cancellationToken)
{
var entity = await _context.Customers
.Where(e =>
e.Username == request.Username
&& e.Password == Encypt.EncryptString(request.Password))
.FirstOrDefaultAsync(cancellationToken);
if (entity.Equals(null))
{
throw new NotFoundException(nameof(entity), request.Username);
}
return new CustomerViewModel
{
ExternalId = entity.ExternalId
};
}
This is the exception I am getting:
Please let me know what else you need to determine what could be the issue. Also, be kind I have been away from c# for a while.
Thanks for the info it was the missing DI. I added this
// Add MediatR
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
services.AddMediatR(typeof(LoginCustomerQueryHandler).GetTypeInfo().Assembly);
and we are good to go.

Web api Data Annotations cause 500 server error when passed

I am creating a web api project and want to add validation to my model, so I added a DataAnnotation attribute.
I then tested the project by trying to pass my object from a separate mvc project. I recieved a 500 server error.
Removing the DataAnnotation allows me to pass the object successfully. Why?
I have looked at a couple of tutorials such as this and this, they show how to handle validation errors, but this has not helped.
UPDATE
Removing the data annotation from MyProperty in Class1, solution B (but leaving it on the class in solution A) means values can be passed successfully! Is this a problem with deserilazing the object? If so how do I solve it?
B = My web service reciving the object
A = My mvc project sending the object
my code to send the resquest
public class Class1
{
[Required(ErrorMessage = "MyProperty value is required")]//remove this line to make it work
public int MyProperty { get; set; }
public Class2 MyOtherProperty { get; set; }
}
public class Class2
{
public string SomeProperty { get; set; }
}
async Task<string> Test2()
{
var form = new Class1();
form.MyProperty = 123;
form.Class2 = new Class2();
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:58814/api/");
var post = await client.PostAsJsonAsync<Class1>("Values", form);
var putt = await client.PutAsJsonAsync<Class1>("Values", form);
}
return "";
}
my code to recieve the request (the breakpoint applied is not being hit)
// POST api/values
public void Post([FromBody]Class1 value)
{
}
// PUT api/values/5
public void Put([FromBody]Class1 value)
{
}
I have resolve the issue thanks to the advice from this link - Scott Hanselman's blog
My sending project and receiving project had identical classes (including data annotations). Removing the annotations from the class in the receiving project resolved the binding issue but I still needed validation on the properties. Also changing the annotation from required to Range also resolved the binding issue (I only mention these as they were some steps I took to try and debug).
The final solution for me was to change this
public class Network
{
[Required(ErrorMessage = "NetworkID is required")]
public int NetworkID { get; set; }
}
to this
[DataContract]
public class Network
{
[DataMember(IsRequired = true)]
public int NetworkID { get; set; }
}

Access TempData in ExecuteResult Asp.Net MVC Core

I wanted to save notification in TempData and shown to user. I create extension methods for this and implement a class which Extends from ActionResult. I need to access TempData in override ExecuteResult method with ActionContext.
Extension Method:
public static IActionResult WithSuccess(this ActionResult result, string message)
{
return new AlertDecoratorResult(result, "alert-success", message);
}
Extends ActionResult class.
public class AlertDecoratorResult : ActionResult
{
public ActionResult InnerResult { get; set; }
public string AlertClass { get; set; }
public string Message { get; set; }
public AlertDecoratorResult(ActionResult innerResult, string alertClass, string message)
{
InnerResult = innerResult;
AlertClass = alertClass;
Message = message;
}
public override void ExecuteResult(ActionContext context)
{
ITempDataDictionary tempData = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionary)) as ITempDataDictionary;
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
InnerResult.ExecuteResult(context);
}
}
Call extension method from controller
return RedirectToAction("Index").WithSuccess("Category Created!");
I get 'TempData ' null , How can I access 'TempData' in 'ExecuteResult' method.
I was literally trying to do the exact same thing today (have we seen the same Pluralsight course? ;-) ) and your question led me to find how to access the TempData (thanks!).
When debugging I found that my override on ExecuteResult was never called, which led me to try the new async version instead. And that worked!
What you need to do is override ExecuteResultAsync instead:
public override async Task ExecuteResultAsync(ActionContext context)
{
ITempDataDictionaryFactory factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(context.HttpContext);
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
await InnerResult.ExecuteResultAsync(context);
}
However, I have not fully understood why the async method is called as the controller is not async... Need to do some reading on that...
I find out the way to get the TempData. It need to get from ITempDataDictionaryFactory
var factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
var tempData = factory.GetTempData(context.HttpContext);

Web API - Access Custom Attribute Properties inside ActionFilterAttribute OnActionExecuting

I need to access a property inside a custom DataAnnotation attribute. How can I access this attribute in order to set the response value? The attribute is added to the model property.
public class BirthDateAttribute : ValidationAttribute
{
public string ErrorCode { get; set; }
....
}
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
List<Errors> errors = new List<Errors>();
// Set error message and errorCode
foreach (var key in keys)
{
if (!actionContext.ModelState.IsValidField(key))
{
error.Add(new HttpResponseError
{
Code = ???????????,
Message = actionContext.ModelState[key].Errors.FirstOrDefault().ErrorMessage
});
}
}
// Return to client
actionContext.Response = actionContext.Request.CreateResponse(
HttpStatusCode.BadRequest, errors);
}
}
}
Assuming that the custom attribute is applied to the controller, you can try following in the OnActionExecuting event. This similar thing works with MVC controller but should work with API controller too.
var att = actionContext.ControllerContext.GetType().GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;
As mentioned by OP, if this is on a class (Model), it should be pretty starightforward because the type is already known. Replace the Model class.
var att = <<ModalClass>>.GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;

Resources