I'm using WebAPI to call some third party methods:
public class SessionsController : ApiController
{
public DataTable Get(int id)
{
return Services.TryCall(es => es.GetSessionList(id).Tables[0]);
}
}
I'm wrapping all calls on this services:
internal static class Services
{
internal static IExternalService ExternalService { get; set; }
internal static T TryCall<T>(Func<IExternalService,T> theFunction)
{
try
{
return theFunction(ExternalService);
}
catch (FaultException<MyFaultDetail> e)
{
var message = new HttpResponseMessage(HttpStatusCode.NotImplemented);
message.Content = new StringContent(e.Detail.Message);
throw new HttpResponseException(message);
}
}
}
When an exception is thrown, it is caught and the message prepared. With rethrowing I get a new Error Message:
Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
How can i return this exception properly, without Visual Studio complaining? When i skip the error, the browser gets an 501 error result.
The Method Request.CreateResponse() is not available in my wrapper method.
Well, while this may work, I recommend you creating an ExceptionFilterAttribute for this. This way, you won't have to guard every method for exception with the same bloated code.
For example:
public class FaultExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is FaultException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
There are several ways you can use this filter:
1. By Action
To apply the filter to a specific action, add the filter as an attribute to the action:
public class SampleController : ApiController
{
[FaultExceptionFilter]
public Contact SampleMethod(int id)
{
//Your call to a method throwing FaultException
throw new FaultException<MyFaultDetail>("This method is not implemented");
}
}
2. By Controller:
To apply the filter to all of the actions on a controller, add the filter as an attribute to the controller class:
[FaultExceptionFilter]
public class SampleController : ApiController
{
// ...
}
3. Globally
To apply the filter globally to all Web API controllers, add an instance of the filter to the GlobalConfiguration.Configuration.Filters collection. Exeption filters in this collection apply to any Web API controller action.
GlobalConfiguration.Configuration.Filters.Add(new FaultExceptionFilterAttribute());
If you use the "ASP.NET MVC 4 Web Application" project template to create your project, put your Web API configuration code inside the WebApiConfig class, which is located in the App_Start folder:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new FaultExceptionFilterAttribute());
// Other configuration code...
}
}
Related
I am new the API in general, let me give you the background of the API and what I want it to do.
I have a API have that are external facing and so every incoming request are required to check the signature from header. literality my code in every controller call are checking the signature and created many duplicated code.
my question is how can reduces those duplicated code ? do I use Custom Attributes, or AuthorizeAttribute
here are some of the example code:
[Route("[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
public async Task<Result> Call_1(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
public async Task<Result> Call_2(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
}
above code is just for showing, the actual Sinature checking logic is around 20 lines of code on every single controller method.
Yes, you can do that using action filters. It's described in documentation
Put your code for checking into OnActionExecuting method. So, you can write Result in the action filter if the signature isn't valid.
In case you need specific result structure you can create your own ObjectResult:
public class ForbiddenObjectResult : ObjectResult
{
public string Message { get; private set; }
public ForbiddenObjectResult(object value, string message)
: base(value)
{
StatusCode = StatusCodes.Status403Forbidden;
Message = message;
}
}
...
string signaturel;
signature = Util.getHeaderSignature(context.HttpContext.Request);
if(!unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
context.Result = new ForbiddenObjectResult(filterContext.ModelState, "InvalidSinaturemessage");
}
And to register it for all your endpoints(if needed):
services.AddControllersWithViews(options =>
{
options.Filters.Add<YourActionFilter>();
});
You can use token based authentication or filter method. For reference
Token based authentication
Custom Filter
I have a logic to apply in case the request received is a BadRequest, to do this I have created a filter:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// Apply logic
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => { options.Filters.Add<ValidateModelAttribute>(); });
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
[Route("test")]
[HttpPost]
[ValidateModel]
public ActionResult<Guid> validationTest(PersonalInfo personalInfo)
{
return null;
}
}
Model:
public class PersonalInfo
{
public string FirstName { get; set; }
[RegularExpression("\\d{4}-?\\d{2}-?\\d{2}", ErrorMessage = "Date must be properly formatted according to ISO 8601")]
public string BirthDate { get; set; }
}
The thing is when I put a break point on the line:
if (!context.ModelState.IsValid)
execution reaches this line only if the request I send is valid. Why it is not passing the filter if I send a bad request?
The [ApiController] attribute that you've applied to your controller adds Automatic HTTP 400 Responses to the MVC pipeline, which means that your custom filter and action aren't executed if ModelState is invalid.
I see a few options for affecting how this works:
Remove the [ApiController] attribute
Although you can just remove the [ApiController] attribute, this would also cause the loss of some of the other features it provides, such as Binding source parameter inference.
Disable only the Automatic HTTP 400 Responses
Here's an example from the docs that shows how to disable just this feature:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// ...
options.SuppressModelStateInvalidFilter = true;
// ...
}
This code goes inside of your Startup's ConfigureServices method.
Customise the automatic response that gets generated
If you just want to provide a custom response to the caller, you can customise what gets returned. I've already described how this works in another answer, here.
An example of intersection for logging is describe in Log automatic 400 responses
Add configuration in Startup.ConfigureServices.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
// Perform logging here.
//E.g. logger.LogError($”{context.ModelState}”);
logger.LogWarning(context.ModelState.ModelStateErrorsToString());
// Invoke the default behavior, which produces a ValidationProblemDetails response.
// To produce a custom response, return a different implementation of IActionResult instead.
return builtInFactory(context);
};
});
public static String ModelStateErrorsToString(this ModelStateDictionary modelState)
{
IEnumerable<ModelError> allErrors = modelState.Values.SelectMany(v => v.Errors);
StringBuilder sb = new StringBuilder();
foreach (ModelError error in allErrors)
{
sb.AppendLine($"error {error.ErrorMessage} {error.Exception}");
}
return sb.ToString();
}
As the attribute filter in the life cycle of the .Net Core you can’t handle it. The filter layer with ModelState will run after the model binding.
You can handle it with .Net Core middleware as the following https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs=aspnetcore2x
If you want to SuppressModelStateInvalidFilter on individual action, consider to use custom attribute suggested on https://learn.microsoft.com/en-us/answers/questions/297568/how-to-suppress-suppressmodelstateinvalidfilter-at.html. (And similar answer https://github.com/aspnet/Mvc/issues/8575)
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
private const string FilterTypeName = "ModelStateInvalidFilterFactory";
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
//if (action.Filters[i] is ModelStateInvalidFilter)
if (action.Filters[i].GetType().Name == FilterTypeName)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
Example of use
[ApiController]
public class PersonController
{
[SuppressModelStateInvalidFilter]
public ActionResult<Person> Get() => new Person();
}
[HttpPost]
[Route("TnC")]
public IHttpActionResult TnC(CustomViewModel myViewModel)
{
try
{
return Json(_Internal.TnC(myViewModel, LoggedInUser));
}
catch (BusinessException exception)
{
return Json(BuildErrorModelBase(exception));
}
}
Where the _Internal is a Service with a guaranteed 99.99% up-time and not formalized fault contract interfaces defined.
Exceptions which are handled in my application level(business layer level) as a BusinessException - root class
Where BusinessException is defined as follows
public class BusinessException : Exception
{
BusinessException()...
BusinessExceptionFoo()...
BusinessExceptionBar()...
//...
}
And the current test Method is
To do : Add Exception test
[TestMethod]
[ExpectedException(typeof(BusinessException),
"Not a valid Business Case")]
public void TnCTest()
{
var bookingService = myContainer.Resolve<mySvc>();
var controller = new LinkBookingController(mySvc, myContainer);
var expectedResult = controller.TnC(new myViewModel
{
...params
});
var actualResult = GetData<Result<myViewModel>>(expectedResult);
Assert.AreEqual(expectedResult, actualResult);
}
expectedResult==actualResult does not test the exception block of the code.
How do I construct a request that makes the service throw the exception other than manually removing the ethernet cable to get this specific type of server error.
The best I could come up with was
#if DEBUG && UnitTestExceptions
throw new BusinessException();
#endif
But there is gotta be a better option.
There are a few things of concern with the method under test.
It is mixing cross-cutting concerns in the action that should be refactored out into an ExceptionHandler. Chances are that piece of code is repeated many times in that controller and others like it (DRY).
public class WebApiExceptionHandler : ExceptionHandler {
public override void Handle(ExceptionHandlerContext context) {
var innerException = context.ExceptionContext.Exception;
// Ignore HTTP errors
if (innerException.GetType().IsAssignableFrom(typeof(System.Web.HttpException))) {
return;
}
if(innerException is BusinessException) {
context.Result = BuildErrorResult(exception);
return;
}
//...other handler code
}
IHttpActionResult BuildErrorResult(BusinessException exception) {
//... your logic here
}
}
The following extension method could be used to add the handler to the HttpConfiguration during startup which also assumes that the application is taking advantage of dependency inversion services.
public static HttpConfiguration ReplaceExceptionHandler(this HttpConfiguration config) {
var errorHandler = config.Services.GetExceptionHandler();
if (!(errorHandler is WebApiExceptionHandler)) {
var service = config.Services.GetService(typeof(WebApiExceptionHandler));
config.Services.Replace(typeof(IExceptionHandler), service);
}
return config;
}
Now that cross-cutting concerns have been dealt with the action becomes a lot simpler and easier to test. This is a simplified example of an ApiController
public class LinkBookingController : ApiController {
private IBookingService bookingService;
public LinkBookingController(IBookingService service) {
bookingService = service;
}
[HttpPost]
[Route("TnC")]
public IHttpActionResult TnC(CustomViewModel myViewModel) {
return Json(bookingService.TnC(myViewModel, User));
}
}
where IBookingService is defined as
public interface IBookingService {
BookingModel TnC(CustomViewModel viewModel, IPrincipal user);
}
Using a mocking framework like Moq an exception can be made to be thrown as needed.
[TestMethod]
[ExpectedException(typeof(BusinessException), "Not a valid Business Case")]
public void TnC_Should_Throw_BusinessException() {
//Arrange
var bookingService = new Mock<IBookingService>();
var controller = new LinkBookingController(bookingService.Object);
var viewModel = new myViewModel
{
//...params
};
bookingService.Setup(_ => _.TnC(viewModel, It.IsAny<IPrincipal>())).Throws<BusinessException>()
//Act
var expectedResult = controller.TnC(viewModel);
//Assert
//...the ExpectedException attribute should assert if it was thrown
}
To test how that exception can be handled do a unit test on the exception handler and not the controller as that is not the responsibility of the controller.
Try to keep controllers lean and focused on its UI concerns.
As Nkosi mentioned in his comment, what you need to do is add an interface to whatever type _Internal is so that your controller now depends on an interface as a contract, rather than the specific implementation.
Next, create a second constructor for your controller that accepts a IInternalService (whatever its called) and assigns that to _Internal. Your parameterless constructor can still assign whatever instance you're using now.
Now that you have this configuration (often called "poor mans dependency injection"), your unit test can create an instance of your controller passing in a different implementation of your service that throws an exception. You can do this by creating a new class, or you can do it dynamically using a library like Moq.
Hope that makes sense.
I have a simple controller :
public class UsersController : ApiController
{
[HttpPost]
[AllowAnonymous]
public HttpResponseMessage Login([FromBody] UserLogin userLogin)
{
var userId = UserCleaner.Login(userLogin.MasterEntity, userLogin.UserName, userLogin.Password, userLogin.Ua);
if (userId == null) return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "User not authorized");
return Request.CreateResponse(HttpStatusCode.OK, Functions.RequestSet(userId));
}
}
As you can see , only POST is currently available .
But when I invoke a GET in a browser (just for checking):
http://royipc.com:88/api/users
I get :
{"Message":"The requested resource does not support http method
'GET'."}
It is clear to me why it happens. But I want to return a custom exception when it happens.
Other answers here at SO doesn't show how I can treat this kind of situation (not that i've found of, anyway)
Question
How (and where) should I catch this kind of situation and return custom exception (HttpResponseMessage) ?
NB
I don't want to add a dummy GET method just for "catch and throw". tomorrow there can be a GET method. I just want to catch this Exception and return my OWN !
You may need to inherit from ApiControllerActionSelector class which is what the Web API uses to select the required action.
then you can replace the default IHttpActionSelector by your new action selector like that. config.Services.Replace(typeof(IHttpActionSelector), new MyActionSelector());
check this url for full example: http://www.strathweb.com/2013/01/magical-web-api-action-selector-http-verb-and-action-name-dispatching-in-a-single-controller/
You can build custom Exception filters in ASP.Net WebAPI. An exception filter is a class that implements the IExceptionFilter interface. To create a custom exception filter you can either implement the IExceptionFilter interface yourself or create a class that inherits from the inbuilt ExceptionFilterAttribute class. In the later approach all you need to do is override the OnException() method and plug-in some custom implementation.
public class MyExceptionFilter:ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An unhandled exception was thrown by the Web API controller."),
ReasonPhrase = "An unhandled exception was thrown by the Web API controller."
};
context.Response = msg;
}
}
you would likely want to test for conditions and generate the exact exception, but this is a bare example.
To use the exception class, you can either register it in the Global.asax, or as an attribute on a specific class or method.
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configuration.Filters.Add(new WebAPIExceptionsDemo.MyExceptionFilter());
AreaRegistration.RegisterAllAreas();
...
}
}
or
[MyExceptionFilter]
public class UsersController : ApiController
{
...
}
I detected a problem in the RequestFilter execution order.
The ValidationFeature in ServiceStack is a Plugin that just registers a Global Request Filter. The Order of Operations points out that Global Request Filters are executed after Filter Attributes with a Priority <0 and before Filter Attributes with a Priority >=0
My BasicAuth filter has -100 priority, and in fact everything goes well if the Service is annotated at class level, but it fails when the annotation is at method level, with the authentication filter being executed after.
I am using 3.9.70
Is there any quick fix for this? Thanks
When you add the annotation at method level then you are creating an Action Request Filter (because you are adding the annotation to an action method) which in the Order of Operations is operation 8, after the other filters have run.
5: Request Filter Attributes with Priority < 0 gets executed
6: Then any Global Request Filters get executed
7: Followed by Request Filter Attributes with Priority >= 0
8: Action Request Filters (New API only)
The best workaround I can suggest is to reconsider your service structure. I imagine you are having these difficulties because you are adding unauthenticated api methods alongside your secure api methods, and thus are using method level attributes to control authentication. So you are presumably doing something like this Your classes and attributes will be different, this is just exemplar:
public class MyService : Service
{
// Unauthenticated API method
public object Get(GetPublicData request)
{
return {};
}
// Secure API method
[MyBasicAuth] // <- Checks user has permission to run this method
public object Get(GetSecureData request)
{
return {};
}
}
I would do this differently, and separate your insecure and secure methods into 2 services. So I use this:
// Wrap in an outer class, then you can still register AppHost with `typeof(MyService).Assembly`
public partial class MyService
{
public class MyPublicService : Service
{
public object Get(GetPublicData request)
{
return {};
}
}
[MyBasicAuth] // <- Check is now class level, can run as expected before Validation
public class MySecureService : Service
{
public object Get(GetSecureData request)
{
return {};
}
}
}
Solution - Deferred Validation:
You can solve your execution order problem by creating your own custom validation feature, which will allow you to defer the validation process. I have created a fully functional self hosted ServiceStack v3 application that demonstrates this.
Full source code here.
Essentially instead of adding the standard ValidationFeature plugin we implement a slightly modified version:
public class MyValidationFeature : IPlugin
{
static readonly ILog Log = LogManager.GetLogger(typeof(MyValidationFeature));
public void Register(IAppHost appHost)
{
// Registers to use your custom validation filter instead of the standard one.
if(!appHost.RequestFilters.Contains(MyValidationFilters.RequestFilter))
appHost.RequestFilters.Add(MyValidationFilters.RequestFilter);
}
}
public static class MyValidationFilters
{
public static void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
// Determine if the Request DTO type has a MyRoleAttribute.
// If it does not, run the validation normally. Otherwise defer doing that, it will happen after MyRoleAttribute.
if(!requestDto.GetType().HasAttribute<MyRoleAttribute>()){
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
return;
}
Console.WriteLine("Deferring Validation until Roles are checked");
}
}
Configure to use our plugin:
// Configure to use our custom Validation Feature (MyValidationFeature)
Plugins.Add(new MyValidationFeature());
Then we need to create our custom attribute. Your attribute will be different of course. The key thing you need to do is call ValidationFilters.RequestFilter(req, res, requestDto); if you are satisfied the user has the required role and meets your conditions.
public class MyRoleAttribute : RequestFilterAttribute
{
readonly string[] _roles;
public MyRoleAttribute(params string[] roles)
{
_roles = roles;
}
#region implemented abstract members of RequestFilterAttribute
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
Console.WriteLine("Checking for required role");
// Replace with your actual role checking code
var role = req.GetParam("role");
if(role == null || !_roles.Contains(role))
throw HttpError.Unauthorized("You don't have the correct role");
Console.WriteLine("Has required role");
// Perform the deferred validation
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
}
#endregion
}
For this to work we need to apply our custom attribute on the DTO route not the action method. So this will be slightly different to how you are doing it now, but should still be flexible.
[Route("/HaveChristmas", "GET")]
[MyRole("Santa","Rudolph","MrsClaus")] // Notice our custom MyRole attribute.
public class HaveChristmasRequest {}
[Route("/EasterEgg", "GET")]
[MyRole("Easterbunny")]
public class GetEasterEggRequest {}
[Route("/EinsteinsBirthday", "GET")]
public class EinsteinsBirthdayRequest {}
Then your service would look something like this:
public class TestController : Service
{
// Roles: Santa, Rudolph, MrsClaus
public object Get(HaveChristmasRequest request)
{
return new { Presents = "Toy Car, Teddy Bear, Xbox" };
}
// Roles: Easterbunny
public object Get(GetEasterEggRequest request)
{
return new { EasterEgg = "Chocolate" };
}
// No roles required
public object Get(EinsteinsBirthdayRequest request)
{
return new { Birthdate = new DateTime(1879, 3, 14) };
}
}
So when we call the route /EinsteinsBirthday which does not have a MyRole attribute the validation will be called normally, as if using the standard ValidationFeature.
If we call the route /HaveChristmas?role=Santa then our validation plugin will determine that the DTO has our attribute and not run. Then our attribute filter triggers and it will trigger the validation to run. Thus the order is correct.