I have a resource manager class for web api project. Like this :
public static class MyResource
{
private static global::System.Globalization.CultureInfo resourceCulture;
public static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
public static string RecordAdded { get { return Content.ResourceManager.GetString("RecordAdded", resourceCulture); } }
}
I want to set resourceCulture in every request. How can i do this with most generic way ?
public class BaseApiController : ApiController
{
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
IEnumerable<string> lang;
controllerContext.Request.Headers.TryGetValues("lang", out lang);
MyResource.Culture = new System.Globalization.CultureInfo(lang.FirstOrDefault());
base.Initialize(controllerContext);
}
}
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
}
This did not work.
By the way, I dont want to use action filters if i dont have to.
Thanks.
I think you can use delegating handler before processing of request. You can find docs here.
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> lang;
request.Headers.TryGetValues("lang", out lang);
MyResource.Culture = new System.Globalization.CultureInfo(lang.FirstOrDefault());
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
Related
My aim is to log when and who is calling the GetAll() functions, as i remember from MVC ActionFilter, it just works that way, but when i try to implement similar filter from razor, the filter is not working? Anyone knows where i did wrong?
I am referring to the documentation of this Filter methods for Razor Pages in ASP.NET Core
I have created the sample.razor page like this, it display list of records.
#page "/sample"
<BlazorAgGrid #ref="AgGrid" RowData="result.Items" TRow="SampleViewModel"
AutoGenerateColumns="false"
Debug="false" Options="AgGridOptions" Attributes="Attributes"
AutoSizeColumns="true">
<BlazorAgGridColumn HeaderName="Sample1" Field="Sample1"/>
<BlazorAgGridColumn HeaderName="Sample2" Field="Sample2"/>
<BlazorAgGridColumn HeaderName="Sample3" Field="Sample3"/>
</BlazorAgGrid>
#code
{
private BlazorAgGrid<SampleViewModel> AgGrid;
public Dictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>()
{
{ "style", "height: 500px" },
{ "class", "ag-theme-balham" }
};
private BlazorAgGridOptions AgGridOptions = new()
{
RowSelection = RowSelection.Single,
SuppressRowDeselection = true,
EnablePagination = false,
EnablePaginationAutoPageSize = false
};
private PagedList<SampleViewModel> result = new();
result = await SampleService.GetAll();
}
here is the GetAll() function
[SampleActionFilter]
public async Task<PagedList<SampleViewModel>> GetAll()
{
return await _httpClient.GetFromJsonAsync<PagedList<SampleViewModel>>
($"sample/list");
}
here is the actionFilter i trying to make
using Microsoft.AspNetCore.Mvc.Filters;
using System.Threading.Tasks;
namespace Pages.Sample
{
public class SampleActionFilter : ResultFilterAttribute
{
public SampleActionFilter()
{
}
public override void OnResultExecuted(ResultExecutedContext context)
{
base.OnResultExecuted(context);
}
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
}
public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
return base.OnResultExecutionAsync(context, next);
}
}
}
I have 2 domains (.com and .ru) and 2 URLs like site.com/about-us and site.ru/o-nas which should be redirected to the same page. The site uses Razor Pages.
Also, the particular URL should be available in the appropriate domain. For example:
site.COM/o-nas should not work and return Not Found (404)
site.RU/about-us should not work and return Not Found (404)
I found that filters work OK, but for both for site.com/about-us and site.ru/o-nas both filters are called.
How to call only 1 for particular URL, is it possible? Thank you, my current code is below.
public static class DomainFilters
{
public static IPageApplicationModelConvention DomainEng(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new EnglishActionFilter(route));
});
}
public static IPageApplicationModelConvention DomainRussian(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new RussianActionFilter(route));
});
}
}
public class EnglishActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".ru"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
And finally ConfigureServices method from Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.DomainEng("/AboutUs", "about-us");
options.Conventions.DomainRussian("/AboutUs", "o-nas");
})
}
Consider implementation of a custom FilterFactory:
public class LanguageFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<IHttpContextAccessor>();
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
return new EnglishActionFilter();
}
return new RussianActionFilter();
}
}
This factory will create either an English or Russian filter (depending on the domain). That's all about its responsibilities. The rest goes to Filters themselves (you'll need to change a code inside the filters to make them validate the page locator):
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// you may want to play with RouteData in order to make this check more elegant
if (context.HttpContext.Request.Path.Value.Contains("About"))
{
context.Result = new NotFoundResult();
}
}
}
The filter factory is applied in the same way as other filters:
[LanguageFilterFactory]
public class IndexModel : PageModel
The Startup.cs file update:
.AddMvcOptions(options =>
{
options.Filters.Add<LanguageFilterFactory>();
});
I am trying to figure out how to implement a Global Exception Handler in .NET Web Api 2.
I tried following the example set out by Microsoft here:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/web-api-global-error-handling
But when exception occured, it did nothing.
This is my code:
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
Trace.WriteLine(context.Exception.Message);
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact support#testme.com so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { private get; set; }
public string Content { private get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response =
new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(Content),
RequestMessage = Request
};
return Task.FromResult(response);
}
}
}
Is there a better way (or more proper way) to implement a global exception handler?
Try adding this to your WebApiConfig
webConfiguration.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler()); // You have to use Replace() because only one handler is supported
webConfiguration.Services.Add(typeof(IExceptionLogger), new MyExceptionLogger()); // webConfiguration is an instance of System.Web.Http.HttpConfiguration
You missed
class GlobalExceptionHandler : ExceptionHandler
{
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
//...
}
See WebApi v2 ExceptionHandler not called
When ovveride the IExceptionHandler, the response does not reach the DelegatingHandler when a unexpected exception occurs. How can I fix this?
In webapi 2, I want to implement a audit logger for request and response messages. I also want to add a global exception handler. However, when I replace the IExceptionHandler with my custom implementation. the response never reaches the DelegatingHandler -on exception - And thus the audit for response is lost.
in WebApiConfig
// add custom audittrail logger
config.MessageHandlers.Add(new AuditLogHandler());
// replace global exception handeling
config.Services.Replace(typeof(IExceptionHandler), new WebAPiExceptionHandler());
Custom Exception Handler
public class WebAPiExceptionHandler : ExceptionHandler
{
//A basic DTO to return back to the caller with data about the error
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new ErrorInformation { Message = "Iets is misgegaan", ErrorDate = DateTime.UtcNow }));
}
}
Custom Auditlogger
public class AuditLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
var task = await request.Content.ReadAsStringAsync();
// .. code for loggign request
}
var result = await base.SendAsync(request, cancellationToken);
// .. code for logging response
// when I do not replace WebAPiExceptionHandler, code is reachred here
// When I Do use WebAPiExceptionHandler, code is not reached here
return result;
}
}
Code for throwing exception in webapi
public class Values_v2Controller : ApiController
{
public string Get(int id)
{
throw new Exception("haha");
}
}
Dont use ExceptionHandler as base class, implement interface IExceptionHandler
public class WebAPiExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var fout = new ErrorInformation
{
Message = "Iets is misgegaan"
, ErrorDate = DateTime.UtcNow
};
var httpResponse = context.Request.CreateResponse(HttpStatusCode.InternalServerError, fout);
context.Result = new ResponseMessageResult(httpResponse);
return Task.FromResult(0);
}
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
}
The problem is that ExceptionHandler only executes Handle(ExceptionHandlerContext context) method if ShouldHandle(ExceptionHandlerContext context) returns true.
Overriding bool ShouldHandle(ExceptionHandlerContext context) to always return true fix the problem for me.
Is there a way I can use the new IHttpActionResult interface to return a HttpStatusCode.NoContent response message?
I am currently using return new HttpResponseMessage( HttpStatusCode.NoContent );
and would like to convert this into return NoContent();.
IHttpActionResult has already got Ok(), Conflict() and NotFound() but I cannot find any for Forbidden() and NoContent() which I need to use in my project.
How easy is it to add other result types?
There's no convenience method for no-content result because, by default, when a action returns void, the response will have the HTTP status 204.
If you wish to explicitly indicate that on the action, you could also return a StatusCode(HttpStatusCode.NoContent) from your action or a
ResponseMessage(new HttpResponseMessage(HttpStatusCode.NoContent)).
The Unauthorized() convenience method gives you a 401 status so, for Forbidden (403), you would also have to use StatusCode(HttpStatusCode.Forbidden) or
ResponseMessage(new HttpResponseMessage(HttpStatusCode.Forbidden))
I found this example site that shows how to add a custom IHttpActionResult method and I've used this to create the Forbidden() and NoContent() methods with great success.
public abstract class CommonApiController : ApiController
{
public class ForbiddenResult : IHttpActionResult
{
private readonly HttpRequestMessage _request;
private readonly string _reason;
public ForbiddenResult(HttpRequestMessage request,string reason)
{
_request = request;
_reason = reason;
}
public ForbiddenResult(HttpRequestMessage request)
{
_request = request;
_reason = "Forbidden";
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = _request.CreateResponse(HttpStatusCode.Forbidden,_reason);
return Task.FromResult(response);
}
}
public class NoContentResult : IHttpActionResult
{
private readonly HttpRequestMessage _request;
private readonly string _reason;
public NoContentResult(HttpRequestMessage request,string reason)
{
_request = request;
_reason = reason;
}
public NoContentResult(HttpRequestMessage request)
{
_request = request;
_reason = "No Content";
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = _request.CreateResponse(HttpStatusCode.NoContent,_reason);
return Task.FromResult(response);
}
}
}
And then I can use it like this:
public class InvoiceController : CommonApiController
{
public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
{
if(User.IsInRole("Readonly"))
{
return Forbidden();
}
// Rest of code
}
}
I tried the #Intrepid implementation and I ran into some problems. I see two solutions here:
Solution 1:
The part: return Forbidden(); should not work.
The compiler would not recognize this.
Instead it should be: return new ForbiddenResult(Request, "my reason");
UPDATE 1
Solution 2:
I think this is what #Interpid intended in his implementation, but he was missing a few things.
In order to use return Forbidden(); the CommonApiController should be updated with the functions that return the custom IHttpActionResult for Forbidden and NoContent
The class should look like this:
public abstract class CommonApiController: ApiController {
protected ForbiddenResult Forbidden() {
return new ForbiddenResult(this.Request);
}
protected ForbiddenResult Forbidden(string reason) {
return new ForbiddenResult(this.Request, reason);
}
protected NoContentResult NoContent() {
return new NoContentResult(this.Request);
}
public class ForbiddenResult: IHttpActionResult {
private readonly HttpRequestMessage _request;
private readonly string _reason;
public ForbiddenResult(HttpRequestMessage request, string reason) {
_request = request;
_reason = reason;
}
public ForbiddenResult(HttpRequestMessage request) {
_request = request;
_reason = "Forbidden";
}
public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
var response = _request.CreateResponse(HttpStatusCode.Forbidden, _reason);
return Task.FromResult(response);
}
}
public class NoContentResult: IHttpActionResult {
private readonly HttpRequestMessage _request;
private readonly string _reason;
public NoContentResult(HttpRequestMessage request, string reason) {
_request = request;
_reason = reason;
}
public NoContentResult(HttpRequestMessage request) {
_request = request;
_reason = "No Content";
}
public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
var response = _request.CreateResponse(HttpStatusCode.NoContent, _reason);
return Task.FromResult(response);
}
}
}
Anyway, if I am wrong and #Interpid's answer is correct. What am I missing here to make his implementation work?
You can now use the following (.Net Standard):
return StatusCode(HttpStatusCode.NoContent);
or (.Net Core 2.1+)
return NoContent();
If you want to include a reason phrase with your response without adding a sub-class to ApiController, build a ResponseMessage object and return it from the action by the ResponseMessage() method. Try this:
public class InvoiceController : ApiController
{
public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
{
if(User.IsInRole("Readonly"))
{
var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
response.ReasonPhrase = "User has the Readonly role";
return ResponseMessage(response);
}
// Rest of code
}
}
This worked well for me:
public class CodeAndReason : IHttpActionResult
{
private readonly HttpStatusCode code;
private readonly string reason;
public CodeAndReason(HttpStatusCode code, string reason)
{
this.code = code;
this.reason = reason;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(code)
{
ReasonPhrase = reason,
Content = new StringContent(reason),
};
return Task.FromResult(response);
}
public static IHttpActionResult NotFound(string reason)
{
return new CodeAndReason(HttpStatusCode.NotFound, reason);
}
public static IHttpActionResult Conflict(string reason)
{
return new CodeAndReason(HttpStatusCode.Conflict, reason);
}
public static IHttpActionResult Unauthorized(string reason)
{
return new CodeAndReason(HttpStatusCode.Unauthorized, reason);
}
}
Used as:
return CodeAndReason.NotFound("Record {blah} not found");