We are testing Azure Communication Services in a new project. Specifically, we are looking at the Azure Communication Services for Calling documented here and the quick start project found here.
The general pattern to utilize the service is shown in the following code.
public string AppCallbackUrl => $"{AppBaseUrl}/api/outboundcall/callback?{EventAuthHandler.GetSecretQuerystring}"
// Defined the call with a Callback URL
var source = new CommunicationUserIdentifier(callConfiguration.SourceIdentity);
var target = new PhoneNumberIdentifier(targetPhoneNumber);
var createCallOption = new CreateCallOptions(
new Uri(AppCallbackUrl),
new List<MediaType> { MediaType.Audio },
new List<EventSubscriptionType> { EventSubscriptionType.DtmfReceived });
// Initiate the call
var call = await callClient.CreateCallConnectionAsync(
source, new List<CommunicationIdentifier>() { target }, createCallOption, reportCancellationToken)
.ConfigureAwait(false);
// Register for call back events
RegisterToCallStateChangeEvent(call.Value.CallConnectionId);
The example uses a configuration value or hardcoded secret key to authenticate the Callback Url, as shown below.
[Route("api/[controller]")]
[ApiController]
public class OutboundCallController : ControllerBase
{
[AllowAnonymous]
[HttpPost("callback")]
public async Task<IActionResult> OnIncomingRequestAsync()
{
// Validating the incoming request by using secret set in app.settings
if (EventAuthHandler.Authorize(Request))
{
...
}
else
{
return StatusCode(StatusCodes.Status401Unauthorized);
}
}
}
public class EventAuthHandler
{
private static readonly string SecretKey = "secret";
private static readonly string SecretValue;
static EventAuthHandler()
{
SecretValue = ConfigurationManager.AppSettings["SecretPlaceholder"] ?? "h3llowW0rld";
}
public static bool Authorize(HttpRequest request)
{
if (request.QueryString.Value != null)
{
var keyValuePair = HttpUtility.ParseQueryString(request.QueryString.Value);
return !string.IsNullOrEmpty(keyValuePair[SecretKey]) && keyValuePair[SecretKey].Equals(SecretValue);
}
return false;
}
public static string GetSecretQuerystring => $"{SecretKey}={HttpUtility.UrlEncode(SecretValue)}";
}
Is there a better way to do this in a production environment? How can I incorporate ASP.NET Core authentication with a Callback?
Related
Similar to Get the full route to current action, but I want to get the route from outside of the controller method.
[ApiController]
public class TestController : ControllerBase {
public IActionResult OkTest() {
return Ok(true);
}
}
Then a test class:
public class TestControllerTests {
private readonly HttpClient _client;
public TestControllerTests() {
_client = TestSetup.GetTestClient();
}
[Test]
public async Task OkTest() {
var path = GetPathHere(); // should return "/api/test/oktest". But what is the call?
var response = await _client.GetAsync(path);
response.EnsureSuccessStatusCode();
}
}
This approach seems to provide desired result. But this basically instantiates the whole application in order to get to the configured services:
private string GetPathHere(string actionName)
{
var host = Program.CreateWebHostBuilder(new string[] { }).Build();
host.Start();
IActionDescriptorCollectionProvider provider = (host.Services as ServiceProvider).GetService<IActionDescriptorCollectionProvider>();
return provider.ActionDescriptors.Items.First(i => (i as ControllerActionDescriptor)?.ActionName == actionName).AttributeRouteInfo.Template;
}
[TestMethod]
public void OkTestShouldBeFine()
{
var path = GetPathHere(nameof(ValuesController.OkTest)); // "api/Values" in my case
}
However I suspect more complex cases will require a bit more massaging.
I'm using the Mediatr library to register and call my RequestHandlers.
Everything went fine until I started reading more about integrated tests.
PLEASE READ AFTER EDIT
I can't call my class which inherits from the RequesHandler.
My class looks like this:
public class MyRequestHandler : RequestHandler<MyRequest, MyResponse>
{
....
}
I'm not using the Meditr async and I'm using .net framework 4.7 instead of asp.net core, so, everything looks like returns me answers for asp.net core.
When I construct MyTestClass, to construct the RequestHandler I have to create a ServiceFactory and maybe this is the problem because I don't know how.
public MyClassTest()
{
ServiceFactory sv = null;
_mediator = new Mediator(sv);
}
EDIT
Providing more info
I have this Handler in my Application Layer
public class LogInUserByFormHandler : RequestHandler<LogInUserByFormRequest, LogInUserByFormResponse>
{
private readonly IValidator<LogInUserByFormRequest> _validator;
public LogInUserByFormHandler(IValidator<LogInUserByFormRequest> validator)
{
_validator = validator;
}
protected override LogInUserByFormResponse Handle(LogInUserByFormRequest request)
{
_validator.ValidateAndThrow(request);
var userInfo = GetUserInfo(request);
ValidateLogInUserByFormRules(userInfo);
var userLoginInfo = GetValidUserLoginInfo(request);
ValidateUserLoginInfoByFormRules(userLoginInfo);
var sessionKey = CreateUserSessionKey(userInfo);
var response = new LogInUserByFormResponse
{
UserName = request.UserName,
SessionKey = sessionKey,
UserId = userInfo.id_usuario
};
return response;
}
//A LOT OF CODE HERE, methods and etc
}
As it's possible to see, it implements the Mediatr.
On my Web Project on Presentation Layer, I used AutoFac to Inject the Handlers, so, any Request I do is always handled by the right method.
All I have to do is call, like this:
var logInByFormRequest = new LogInUserByFormRequest
{
UserName = viewModel.UserName,
Password = viewModel.Password
};
var response = _mediator.Send(logInByFormRequest).Result;
This works like a charm. The problem now is on the Test project. It references the Application as the Presentation Project does.
I don't know how to make the mediator.send find the right method.
EDIT²
Here comes my test code
[TestClass]
public class LogInUserByFormTest
{
private LogInUserByFormRequest CreateRequest(string userName, string password)
{
LogInUserByFormRequest request = new LogInUserByFormRequest
{
UserName = userName,
Password = password
};
return request;
}
[TestMethod]
[Description("")]
public void UserName_ShouldHave_Max_30Characters_Exception()
{
try
{
var request = CreateRequest("UserNameIsGreaterThanAllowed", "password");
var mediator = new Mock<IMediator>();
var response = mediator.Object.Send(request).Result;
}
catch (System.Exception ex)
{
throw;
}
}
}
The result (response) is always null and the mediator doesn't call the right handler.
EDIT3
Here is how I register the handlers and validators.
I use autofac. This class here is called on the global.asax
public class AutofacConfig
{
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder.RegisterType<Mediator>().As<IMediator>().InstancePerLifetimeScope();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<RegistryManagerService>().As<IRegistryManagerService>().SingleInstance().WithParameter("appName", ConfigurationManager.AppSettings["APPNAME"]);
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.RegisterAssemblyTypes(Assembly.Load("Docspider.Application"))
.Where(x => x.Name.EndsWith("Handler"))
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(Assembly.Load("Docspider.Application"))
.Where(x => x.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
if (_context.TryResolve(validatorType, out object instance))
{
var validator = instance as IValidator;
return validator;
}
return null;
}
}
For such an integration test you would need to configure the necessary dependencies. Since you have indicated that Autofac is being used then configure a container just as you would have in production. Use the container to get the mediator and perform the desired test.
For example.
[TestClass]
public class LogInUserByForm_IntegrartionTest {
private LogInUserByFormRequest CreateRequest(string userName, string password) {
LogInUserByFormRequest request = new LogInUserByFormRequest {
UserName = userName,
Password = password
};
return request;
}
IMediator BuildMediator() {
//AutoFac
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
var mediatrOpenTypes = new[] {
typeof(IRequestHandler<,>)
};
foreach (var mediatrOpenType in mediatrOpenTypes) {
builder
.RegisterAssemblyTypes(typeof(LogInUserByFormRequest).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
builder.Register<ServiceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
//...all other needed dependencies.
//...
var container = builder.Build();
var mediator = container.Resolve<IMediator>();
return mediator;
}
[TestMethod]
[Description("")]
public async Task UserName_ShouldHave_Max_30Characters_Exception() {
try
{
//Arrange
var request = CreateRequest("UserNameIsGreaterThanAllowed", "password");
var mediator = BuildMediator();
//Act
var response = await mediator.Send(request);
//Assert
//...assert the expected values of response.
}
catch (System.Exception ex)
{
throw;
}
}
}
The above was modeled after the examples provided by MediatR.Examples.Autofac
I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
I'm encountering an issue with CORS while using IAsyncResourceFilter implementation.
I want to be able to call my actions from other domains as well...
I've defined the CORS policy under my Startup file as the following:
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
});
});
And under the Configure method:
app.UseCors("AllowAllOrigins");
It works fine without using a TypeFilterAttribute which use IAsyncResourceFilter.
For example calling my API action without any TypeFilterAttribute attribute works:
public bool Get()
{
return true;
}
But when adding my TypeFilterAttribute as follows it doesn't work and returns the error about the CORS:
[MyTypeFilterAttribute("test")]
public bool Get()
{
return true;
}
Anything I'm missing? What should I add when using IAsyncResourceFilter?
The following is the MyTypeFilterAttribute code: (With no real logic...)
public class MyTypeFilterAttribute : TypeFilterAttribute
{
public MyTypeFilterAttribute(params string[] name) : base(typeof(MyTypeFilterAttributeImpl))
{
Arguments = new[] { new MyTypeRequirement(name) };
}
private class MyTypeFilterAttributeImpl: Attribute, IAsyncResourceFilter
{
private readonly MyTypeRequirement_myTypeRequirement;
public MyTypeFilterAttributeImpl(MyTypeRequirement myTypeRequirement)
{
_myTypeRequirement= myTypeRequirement;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
context.Result = new OkResult();
await next();
}
}
}
public class MyTypeRequirement : IAuthorizationRequirement
{
public string Name { get; }
public MyTypeRequirement(string name)
{
Name = name;
}
}
Cors middleware sets headers on the response result object.
I believe you are resetting these with context.Result = new OkResult();
See poke's reply below. If you set any result in an action filter, this result gets sent back immediately, thus overwriting any other one!
I need to call a third party webservice from my asp.net web api. The webservice requires authentication and sends an encrypted password on success which needs to be appended to any further requests to web service.issue here is should i create a new instance of webservice in the controller or have one static instance of the webservice that is referenced by the controller.
I am hosting my webapi using OWIN self hosting as a console application.So in my Program.cs I have
public static WebServiceRequests mWebServiceRequest;
static void Main(string[] args)
{
string baseAddress = "http://localhost:9000/";
mWebServiceRequest = new WebServiceRequests();
// Start OWIN host
using (WebApp.Start<Startup>(url: baseAddress))
{
// Create HttpCient and make a request to api/values
// HttpClient client = new HttpClient();
// var response = client.GetAsync(baseAddress + "api/values").Result;
// Console.WriteLine(response);
// Console.WriteLine(response.Content.ReadAsStringAsync().Result);
Console.ReadLine();
}
}
And my controller goes like
public class MFOrdersController : ApiController
{
public IEnumerable<string> Get()
{
Program.mWebServiceRequest.GetData();
return new string[] { "value3", "value4" };
}
}
Is this the correct way to go about or should it be
public class MFOrdersController : ApiController
{
WebServiceRequests mWebServiceRequest = new WebServiceRequests();
public IEnumerable<string> Get()
{
mWebServiceRequest.GetData();
return new string[] { "value3", "value4" };
}
}
The webservicerequest class is as follows
class WebServiceRequests
{
private xstarmf.MFOrderEntryClient mXStarMFService;
private string strEncryptedPassword;
private string strPassKey;
public WebServiceRequests()
{
mXStarMFService = new xstarmf.MFOrderEntryClient();
x
GeneratePassKey();
GetPassword("TEst", "test123", strPassKey);
}
I need to pass strEncryptedPassword which I get from GetPassword function in every request (GetData()), xstarmf is a an svcclient
Thanks