Web Api 2 : How to Set Resource Manager Culture in Every Request? - c#

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

ActionFilter for functions in razor project is not working

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);
}
}
}

Call particular filter for particular Razor Pages Route?

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>();
});

Global Exception Handling Web Api 2

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

using global exception handeling messes up DelegatingHandler

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.

Creating new IHttpActionResult action result methods

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");

Categories

Resources