Send Twilio a redirect Uri to another action in the same controller - asp.net-core-mvc

I'm trying to send Twilio a redirect URI to another action in the same controller. I can't give a fully formed Uri because I'm in development and I'm using localhost. I thought I would use a relative Uri. The way it is written, I keep getting fed back in to "Welcome" when I'm trying to redirect to "RouteCall".
As a side note, routing in MVC seems very redundant. I couldn't get routing to work without explicitly using the Route tags that you see.
using System;
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Core;
using Twilio.TwiML;
namespace centurionvoice.Controllers
{
[Route("[controller]")]
[ApiController]
public class VoiceController : TwilioController
{
[Route("welcome")]
public IActionResult Welcome()
{
Uri newUri = new Uri("/RouteCall", UriKind.Relative);
var response = new VoiceResponse();
response.Say("Thank you for calling. To do some thing, press 1. To do another thing, press 0.");
response.Gather(numDigits: 1);
response.Redirect(newUri);
return TwiML(response);
}
[Route("routecall")]
[AcceptVerbs("GET", "POST")]
public IActionResult RouteCall(string digits)
{
var response = new VoiceResponse();
if (digits.Equals("1"))
{
//Dial the someone else
response.Say("You are being trasferred.");
return TwiML(response);
}
else
{
//Record a message
response.Say("Please record your message.");
response.Gather();
return TwiML(response);
}
}
}
}

The first thing I did was modify Startup.cs to configure basic/intuitive routing.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "api/{controller=Voice}/{action=Index}/{id?}");
});
}
Then back in VoiceController, new Uri() didn't throw errors anymore:
[HttpPost]
public IActionResult Index()
{
var response = new VoiceResponse();
var gather = new Gather(numDigits: 1, action: new Uri("/api/voice/gather", UriKind.Relative));
gather.Say("To do one thing, press 1. To do another thing, press 0.", voice: "Polly.Nicole");
response.Append(gather);
// If the user doesn't enter input, loop
response.Redirect(new Uri("/api/voice", UriKind.Relative));
return TwiML(response);
}

Related

What should I include in my ASP.NET Core 6 MVC application's program.cs?

I've migrated an ASP.NET MVC application to .NET 6 and notice that during testing, the arguments to an action method/controller uses query string rather than the usual REST-style URL.
For example, where the existing ASP.NET MVC 4 application would use the following style of URL
http://webserver/controller/action/123
the migrated web application uses
http://webserver/controller/action?id=123
I created the migrated app from scratch using the template for an ASP.NET Core 6 MVC application.
The program.cs is as follows:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// doesn't affect the app if it isn't hosted on IIS
builder.WebHost.UseIIS();
builder.WebHost.UseIISIntegration();
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services
.AddControllers()
.AddMvcOptions(opts =>
{
opts.MaxModelBindingCollectionSize = 10000;
});
builder.Services
.AddRazorPages()
.AddRazorRuntimeCompilation();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
try
{
Log.Information("Application starting up");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application startup failed");
}
finally
{
Log.CloseAndFlush();
}
}
The Home view renders links to the other controllers using Html helpers such as
#Html.ActionLink("Vendors", "Index", "VendorController", new { #Id = 15153, #Code = "AGR3" }, null)
and the VendorController action method is
[HttpGet]
public ActionResult Index(int id, string code)
{
// model is a POCO class
var model = _dataService.GetVendorInfo(id, code);
return View("Index", model);
}
How do I get my migrated ASP.NET Core MVC application to not use query string for passing arguments? Do I need to decorate the action method with some kind of routing attribute? I thought I could rely on convention-based routing as per the route pattern declared in the call to app.UseEndpoint in the Main() method of Program.cs?
In asp.net core,you could regist mutipule route patterns
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "myfirstroute",
pattern: "{controller}/{action}/{id?}/{Code?}");
app.MapControllerRoute(
name: "mysecondroute",
pattern: "{controller}/{action}/{id?}");
Specific the name of the route parttern with asp-route and add the required routedata as below(if your dictionary contains the key-values pairs whose key was not registed as a section in your route pattern,it would be added to the query part)
#{
var routedatadic = new Dictionary<string,string>();
routedatadic.Add("controller", "Vendor");
routedatadic.Add("action", "Index");
routedatadic.Add("id", "123");
routedatadic.Add("Code", "ABC");
}
<a asp-route="myfirstroute" asp-all-route-data="#routedatadic">Vendor1</a>
<a asp-route="mysecondroute" asp-all-route-data="#routedatadic">Vendor2</a>
The result:
You could check the document related

C# Web API 405 method not routing to my PUT, always hitting my GET

I have been working with Web API for well over a year, and haven't run into this problem before. I am really at my witt's end after spending hours googling, and looking at stack overflow posts etc. I am wondering if I just have some brain fart thing going on.
I have a controller where I want a get and a put against the same route:
[Route("api/strings")]
[HttpGet]
public IHttpActionResult GetStrings()
{
try
{
var siteStrings = svc.GetSiteStrings(_appId);
return Ok(new { strings = siteStrings });
}
catch(Exception)
{
return InternalServerError();
}
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings")]
public IHttpActionResult PutString(String key, String text)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(key, text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
My routing is just the default out of the box routing as can be seen here:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
After a seeming eternity doing configs over and over based on so many other stack overflow questions about 405 errors, I realized that I can verify that mine is trying to route my PUT method to my GET's endpoint. However if I change the route on my put to something else, I always get a 404. I can't figure out why it is putting all my puts or posts through to my GET verb and thus saying that the action is not allowed on my route. I am sure I am just missing something trivial at this point but mental fatigue and tired eyes are just holding me back. Anyone see something stupid I am doing wrong?
All I had to do was create a class that my put has to model bind to rather than two parameters, and the routing started working. That is surprising, but I just had to do the following and it works:
public class temp
{
public String key { get; set; }
public String text { get; set; }
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings/")]
public IHttpActionResult PutString(temp data)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(data.key, data.text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
Obviously I won't keep that temp class there, but it was a quick stab in the dark to see if the put would route correctly that way. Sure enough it did. Must be something in the model binding specs for Web API that I wasn't aware of.

ASP.net 5 Web API Post CreatedAtRoute always returns 500 Internal Server Error

The database works. It does actually insert the new record, but when I use CreatedAtRoute(), I always get a 500 back from the client. Why?
My controller's Get:
[Route("api/[controller]")]
public class IngredientController : Controller
{
private SimpleCookbookDbContext db { get; set; }
public IngredientController(SimpleCookbookDbContext context)
{
db = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Ingredient>> Get()
{
return await db.Ingredients.ToListAsync();
}
// GET api/values/5
[HttpGet("{id}", Name = "GetIngredient")]
public async Task<Ingredient> Get(int id)
{
return await db.Ingredients.SingleOrDefaultAsync(i => i.Id == id);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Ingredient ingredient)
{
try
{
var res = await IM.CreateAsync(ingredient);
if (!res.Success)
{
return HttpBadRequest(res.Errors);
}
}
catch(Exception)
{
return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
}
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id });
}
}
I tried debugging this. Yes, it would return the HttpBadRequest if the ingredient I'm trying to insert already exists.
I tried putting a breakpoint inside the catch block, and I'm not getting there, so I assume there was no error from the database.
The record does get inserted to the database. I do get to the line return CreatedAtRoute(...); but I get a 500 back. (I set a breakpoint there, too).
Now, I'm using fiddler. My request is this:
POST /api/ingredient HTTP/1.1
Host: localhost:55303
Content-Type: application/json;charset=utf-8
{"id":0, "name": "rosemary", "description": "rosemary"}
I also removed the double quotes on the property names, and I still get the same 500.
I do have camel-casing resolved at the Startup:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
I think I showed all relevant code. If you need more, please let me know.
UPDATE
CreatedAtRoute has an overload taking in three parameters:
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id }, ingredient);
The last parameter is an object, which you could create dynamically or pass back your entire entity, depending on what you want to expose back.
It's strange how there's a 2-parameter variant that would result in a strange 500 response.

Using Action link with Web Api Controller

How can I use ActionLink when calling an action on a controller that is a WebApi controller.
public class RequestController : ApiController
{
[ActionName("CreateAction")]
[ResponseType(typeof(Request))]
public async Task<IHttpActionResult> PostRequest(Request request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Requests.Add(request);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = request.Id }, request);
}
}
in my layout page can I do this
#Html.ActionLink("Request", "CreateAction", "api/Request", new { area = "" }, null)
Jawahar
You can generate a link to an API controller using the standard Url helper property. The following link will point to an Albums controller and pass along an id parameter with a value of 3:
#Url.RouteUrl("DefaultApi", new { httproute=true, controller="Albums", id=3})
Please refer to this link
http://odetocode.com/blogs/scott/archive/2013/03/27/webapi-tip-5-generating-links.aspx

Using WebAPI in LINQPad?

When I tried to use the Selfhosted WebAPI in LINQPad, I just kept getting the same error that a controller for the class didn't exist.
Do I have to create separate assemblies for the WebAPI (Controllers/Classes) and then reference them in my query?
Here's the code I'm using
#region namespaces
using AttributeRouting;
using AttributeRouting.Web.Http;
using AttributeRouting.Web.Http.SelfHost;
using System.Web.Http.SelfHost;
using System.Web.Http.Routing;
using System.Web.Http;
#endregion
public void Main()
{
var config = new HttpSelfHostConfiguration("http://192.168.0.196:8181/");
config.Routes.MapHttpAttributeRoutes(cfg =>
{
cfg.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
});
config.Routes.Cast<HttpRoute>().Dump();
AllObjects.Add(new UserQuery.PlayerObject { Type = 1, BaseAddress = "Hej" });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
using(HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Server open, press enter to quit");
Console.ReadLine();
server.CloseAsync();
}
}
public static List<PlayerObject> AllObjects = new List<PlayerObject>();
public class PlayerObject
{
public uint Type { get; set; }
public string BaseAddress { get; set; }
}
[RoutePrefix("players")]
public class PlayerObjectController : System.Web.Http.ApiController
{
[GET("allPlayers")]
public IEnumerable<PlayerObject> GetAllPlayerObjects()
{
var players = (from p in AllObjects
where p.Type == 1
select p);
return players.ToList();
}
}
This code works fine when in a separate Console Project in VS2012.
I started using AttributeRouting via NuGET when I didn't get the "normal" WebAPI-routing to work.
The error I got in the browser was: No HTTP resource was found that matches the request URI 'http://192.168.0.196:8181/players/allPlayers'.
Additional error: No type was found that matches the controller named 'PlayerObject'
Web API by default will ignore controllers that are not public, and LinqPad classes are nested public, we had similar problem in scriptcs
You have to add a custom controller resolver, which will bypass that limitation, and allow you to discover controller types from the executing assembly manually.
This was actually fixed already (now Web API controllers only need to be Visible not public), but that happened in September and the latest stable version of self host is from August.
So, add this:
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
And then register against your configuration, and you're done:
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
Here is a full working example, I just tested against LinqPad. Note that you have to be running LinqPad as admin, otherwise you won't be able to listen at a port.
public class TestController: System.Web.Http.ApiController {
public string Get() {
return "Hello world!";
}
}
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
async Task Main() {
var address = "http://localhost:8080";
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
conf.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var server = new HttpSelfHostServer(conf);
await server.OpenAsync();
// keep the query in the 'Running' state
Util.KeepRunning();
Util.Cleanup += async delegate {
// shut down the server when the query's execution is canceled
// (for example, the Cancel button is clicked)
await server.CloseAsync();
};
}

Resources