Passing config properties to controllers - asp.net-web-api

I'm self-hosting Owin in a Windows service that provides some functionality and access to other Windows for other functionalities. I have a provider that's starting the Owin app and is also the entry point to all of the system's functionalities.
How can I pass a reference to my provider to each of the controllers? The ApiController class provides Configuration.Properies, but I can't find a way to add the provider reference to the configuration properties. The StartOptions class has Settings, but it only takes string values. I also don't see a way to pass an extra parameter to the Startup class' Configuration method.
public class Provider : BaseProvider
{
public override void StartUp()
{
var options = new StartOptions();
//StartOptions.Settings only allows string values
//I need to pass this provider to the controllers
WebApp.Start<StartUp>("http://SVS:1234");
}
}
internal class StartUp
{
private Provider _provider = null;
internal void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "POSEngineAPI",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//??? How can I set _provider when calling Startup.Config?
config.Properties.AddOrUpdate("Provider", _provider, (key, value) => _provider);
app.UseWebApi(config);
}
}
internal class SessionController : ApiController
{
[HttpPost]
public IHttpActionResult PostSession(Session session)
{
var provider = GetProvider();
//TODO
return Ok(session);
}
[HttpPut]
public IHttpActionResult PutSession(Session session)
{
var provider = GetProvider();
//TODO
return Ok(session);
}
private WebApiSignalRProvider GetProvider()
{
return Configuration.Properties["Provider"] as Provider;
}
}
I'm new to Web Api and Owin so I'm probably missing something obvious.

Related

How to get IOptions in ConfigureServices method or pass IOptions into extension method?

I'm developing asp .net core web api 2.1 app.
I add JWT authentication service as an extension method in static class:
public static class AuthenticationMiddleware
{
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, string issuer, string key)
{
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// validate the server that created that token
ValidateIssuer = true,
// ensure that the recipient of the token is authorized to receive it
ValidateAudience = true,
// check that the token is not expired and that the signing key of the issuer is valid
ValidateLifetime = true,
// verify that the key used to sign the incoming token is part of a list of trusted keys
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
ValidAudience = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
return services;
}
}
which I use in ConfigureServices method of Startup class like this:
public void ConfigureServices(IServiceCollection services)
{
// adding some services omitted here
services.AddJwtAuthentication(Configuration["Jwt:Issuer"], Configuration["Jwt:Key"]);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Now, I have a requirement to use IOptions pattern to get JWT authentication data from appsettings.json
How can I get IOptions in ConfigureServices method to pass issuer and key into extension method? Or how to pass IOptions to extension method?
For binding data from appsettings.json to Model, you could follow steps below:
Appsettings.json content
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"JWT": {
"Issuer": "I",
"Key": "K"
}
}
JWT Options
public class JwtOptions
{
public string Issuer { get; set; }
public string Key { get; set; }
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<JwtOptions>(Configuration.GetSection("JWT"));
var serviceProvider = services.BuildServiceProvider();
var opt = serviceProvider.GetRequiredService<IOptions<JwtOptions>>().Value;
services.AddJwtAuthentication(opt.Issuer, opt.Key);
services.AddMvc();
}
One more option to pass JwtOptions directly.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<JwtOptions>(Configuration.GetSection("JWT"));
var serviceProvider = services.BuildServiceProvider();
var opt = serviceProvider.GetRequiredService<IOptions<JwtOptions>>().Value;
services.AddJwtAuthentication(opt);
services.AddMvc();
}
Change the extension method.
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, JwtOptions opt)
One other option is to bind the configurations to a class with the Bind() extension. (IMO this a more clean solution then the IOptions)
public class JwtKeys
{
public string Issuer { get; set; }
public string Key { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
var jwtKeys = new JwtKeys();
Configuration.GetSection("JWT").Bind(JwtKeys);
services.AddJwtAuthentication(jwtKeys);
}
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, JwtKeys jwtKeys)
{....}
Then if you need the JwtKeys settings some other place in the solution, just register the class on the collection and inject it where needed
services.AddSingleton(jwtKeys);
You can add your options to DI container in Startup class like this:
public class JwtOptions
{
public string Issuer { get; set; }
public string Key { get; set; }
}
public void ConfigureService(IServiceCollection services)
{
services.AddOptions();
services.Configure<JwtOptions>(Configuration.GetSection("Jwt"));
}
Now you can use this options, in a configure stage, or in an extension method:
public void Configure(IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<JwtOptions>();
// write your own code
}

How do I route to a method in ASP.NET Web API?

According to the documentation, if I have this in my WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I should be able to route to a method in my API controller using a URL like this:
http://localhost:55601/api/Customers/Search
Here is my method:
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult Search([FromBody]CustomerDTO SearchTerm)
{
string Name = SearchTerm.Name;
string Email = SearchTerm.Email;
string PhoneNumber = SearchTerm.Phone;
var customer = db.Customers.Single(c => c.Name == Name && c.EmailAddress == Email && c.PhoneNumber == PhoneNumber);
return Ok(customer.id);
}
I'm sending the search data as a JSON object (using HTTP POST method) in the request body.
However, I get an error saying:
Multiple actions were found that match the request
I only have one method in this controller called Search.
I would have thought this should be pretty straightforward, and work the same way it does with MVC controllers. But I think I'm missing something obvious. Can anyone tell me what it is?
EDIT: As per #KevinLaw's request, adding code for controller showing upblic methods. Also, for further information the following request (HTTP GET) works as expected:
http://localhost:55601/api/Customers?email=[recipient#domain]
public class CustomersController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Customers
public IQueryable<Customer> GetCustomers()
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomerByEmail(string email)
{
//...
}
// PUT: api/Customers/5
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
//...
}
// POST: api/Customers
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
//...
}
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult SearchCustomer([FromBody]CustomerDTO SearchTerm)
{
//...
}
// DELETE: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
//...
}
}
The problem here is that the WebApiController uses the REST API specs.
Which state that in a Web Api Controller there can be Zero - One Http Verb.
What i mean by that is that you can have one GET,PUT,POST,DELETE,PATCH
The reason you don't have any problem with the GET is because you have them correctly overloaded. () (int) (string).
But your Posts is (Customer) (CustomerDTO) They are both complex objects and the Binder cannot identify which is which when binding to the complex object.
For this to work you need to use Route Attributes or explicit route.
Attribute Routing
Explicit Routing pt1
Explicit Routing pt2
I think the links are enough to get you started.
If you still want to see some code on your specific case leave a comment below and i will give you some examples.
Thanks
EDIT: Added Examples
Attribute Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
}
On Controller
[RoutePrefix("api/test")]
public class TestingController : ApiController
{
[HttpGet]
[Route("")]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
[Route("search")]
public IHttpActionResult Post([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}
Explicit Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes config.Routes.MapHttpRoute(
name: "SearchTest",
routeTemplate: "api/test/search",
defaults: new { controller = "Testing", action = "Search" }
);
config.Routes.MapHttpRoute(
name: "TestingController",
routeTemplate: "api/test/{id}",
defaults: new { controller = "Testing", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
On Controller
public class TestingController : ApiController
{
[HttpGet]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
public IHttpActionResult Search([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}

Web api 2 attribute routing not working even with it configured in WebApiConfig

I don't understand why my attribute routing isn't working. If I go to 'http://localhost:56125/api/Contact/1682' then I get the test controller with "value1" and "value2" shown, but if I go to http://localhost:56125/api/contacts/1682 then I get No HTTP resource was found that matches the request URI 'http://localhost:56125/api/contacts/1682' and I don't understand why?
In ContactController.cs:
public class ContactController : ApiController
{
private readonly NGSystemRepository _repo = new NGSystemRepository();
// GET: api/Contact
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Route("api/contacts/{contactId}")]
[HttpGet]
public ContactInformation GetContactInformation(int contactNumber)
{
return _repo.GetContact(contactNumber);
}
}
In WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Argh! Stupid mistake. I didn't match the attribute property name (contactId) with the method property name (contactNumber).
My controller action should simply be:
[Route("api/contacts/{contactId}")]
[HttpGet]
public ContactInformation GetContactInformation(int contactId)
{
return _repo.GetContact(contactNumber);
}

No HTTP resource was found that matches the request URI error in ASP.NET Web API

This is a sketch of my TransferController class.
All this is Web API code.
public class TransferController : ApiController
{
[HttpGet, ActionName("Queue")]
public IEnumerable<object> GetQueue(Guid sessionId) {...}
[HttpDelete, ActionName("Delete")]
public void Delete(Guid sessionId, Guid fileId) {...}
[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid fileId) {...}
[HttpGet, ActionName("UploadedBytes")]
public long GetUploadedByteCount(Guid sessionId, Guid fileId) {...}
[HttpGet, ActionName("DownloadUrl")]
public string GetDownloadUrl(string fileId) {...}
[HttpPost, ActionName("FileChunk")]
public void PostFileChunk([FromUri]Guid sessionId, [FromUri]Guid fileId) {...}
[HttpPost, ActionName("UploadDefinition")]
public Guid PostUploadItem([FromBody]UploadDefinition uploadDef) {...}
}
This is the routing.
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultApiDefaultMethod",
routeTemplate: "api/{controller}"
);
}
This is the invocation.
$.ajax({
url: "api/Transfer/Queue",
data: { sessiondId: login.SessionId() }
})
.done(function (result) {
history.push(new UploadItem());
for (var i = 0; i < result.length; i++) {
var ui = new UploadItem(result[i]);
history.push(ui);
}
})
.fail(function (result) {
app.showMessage(JSON.parse(result.responseText).Message);
});
And this is the result.
No HTTP resource was found that matches the request URI 'http://localhost:54770/api/Transfer/Queue?sessiondId=0e2c47b9-e674-446d-a06c-ce16932f9580'.
This is a sketch of my UserController class.
public class UserController : ApiController
[HttpGet, ActionName("Authenticate")]
public object Authenticate(string email, string password) {...}
[HttpPost]
public void Register([FromBody]UserDefinition userDef) {...}
[HttpGet, ActionName("Pulse")]
public bool Pulse(Guid sessionId) {...}
}
For reasons unfathomable to me, I have no trouble calling anything in the UserController. Parameters are marshalled in exactly the same way, and the same routes are in use.
Darrel Miller below uses unit testing to validate routes. Frankly I'm kicking myself for not thinking of this, and now I've done the same.
But tests as he shows them really test only parsing of the URL. For example, this test passes
public void TestMvc4RouteWibble()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var route =
config.Routes.GetRouteData(new HttpRequestMessage()
{
RequestUri = new Uri("http://localhost:54770/api/Transfer/Wibble?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580&fileId=0e2c47b9-e674-446d-a06c-ce16932f9581") //?
});
Assert.IsNotNull(route);
Assert.AreEqual("Transfer", route.Values["controller"]);
Assert.AreEqual("Wibble", route.Values["action"]);
}
despite the conspicuous absence of a method Wibble on the Transfer controller.
Also the route object is not actually a HttpRoute object, it's a HttpRouteData object. But that's trivially corrected. The HttpRoute object is available as a property of the HttpRouteData object.
public void TestMvc4RouteWibble()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var routeData =
config.Routes.GetRouteData(new HttpRequestMessage()
{
RequestUri = new Uri("http://localhost:54770/api/Transfer/Wibble?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580&fileId=0e2c47b9-e674-446d-a06c-ce16932f9581") //?
});
Assert.IsNotNull(routeData);
Assert.AreEqual("Transfer", routeData.Values["controller"]);
Assert.AreEqual("Wibble", routeData.Values["action"]);
}
And it in turn has a Handler property. However this is less informative than it might be, since a null handler simply means (from MSDN)
If null, the default handler dispatches messages to implementations of IHttpController.
Now, my controller is derived from ApiController which certainly implements the ExecuteAsync method that is the only thing specified by the IHttpController interface. Which I imagine means I could test execution of that method if I knew more about it.
Here is a test that demonstrates the routing works ok,
[Fact]
public void TestRoute()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
var route =
config.Routes.GetRouteData(new HttpRequestMessage()
{
RequestUri = new Uri("http://localhost:54770/api/Transfer/Queue?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580") //?
});
Assert.NotNull(route);
Assert.Equal("Transfer",route.Values["controller"]);
Assert.Equal("Queue",route.Values["action"]);
}
and here is a test showing the dispatching/action selection is also working,
[Fact]
public void TestDispatch()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
var server = new HttpServer(config);
var client = new HttpClient(server);
var response =
client.GetAsync(new Uri("http://localhost:54770/api/Transfer/Queue?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580")) //
.Result;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
public class TransferController : ApiController
{
[HttpGet]
[ActionName("Queue")]
public IEnumerable<object> Queue(Guid sessionId)
{
return null;
}
}
OK then... thanks for putting the unit test idea in my head, it sped things up immensely.
Here's the lowdown:
You can have identical parameter signatures on different verbs (get post put delete).
You cannot have identical parameter signatures on different action names on the same verb.
You only need vary one parameter name.
So this is ok because they're all on different verbs
[HttpDelete, ActionName("Delete")]
public void Delete(Guid sessionId, Guid fileId) {...}
[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid fileId) {...}
[HttpPost, ActionName("FileChunk")]
public void PostFileChunk(Guid sessionId, Guid fileId) {...}
but this is not cool because they're both gets
[HttpGet, ActionName("UploadedBytes")]
public long GetUploadedByteCount(Guid sessionId, Guid fileId) {...}
[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid fileId) {...}
and you can fix it like this
[HttpGet, ActionName("UploadedBytes")]
public long GetUploadedByteCount(Guid sessionId, Guid uploadBytesFileId) {...}
[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid cancelFileId) {...}
Maybe I'm a hard-ass but as far as I'm concerned it isn't routing until the method is called.

Property injection in to Web Api controller using Autofac

I'm trying to set a property on an System.Web.Http.ApiController to a value of a resolved IServerPackageRepository. The controller runs in a HttpSelfHostServer and the DependencyResolver has been set to AutofacWebApiDependencyResolver. Here is the code from the Autofac.Module.Load method
...
builder.RegisterType<ServerPackageRepository>()
.As<IServerPackageRepository>()
.SingleInstance()
.WithParameter("path", this.StoragePath);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly())
.PropertiesAutowired();
The ApiController controller itself has a property of type
public IServerPackageRepository Repository { get; set; }
but is never resolved.
I am trying to do it this way because ApiController won't take nothing but default constructors. Any suggestions on how to do this the correct way using Autofac?
If the ApiController is only using the default constructor is sounds like the dependency resolver is not being called and may not be registered with Web API correctly. Here is a working example of self-hosting with constructor injection.
The dependency (in this case a simple logger):
public interface ILogger
{
void Log(string text);
}
public class Logger : ILogger
{
public void Log(string text)
{
Debug.WriteLine(text);
}
}
A simple controller with a dependency on the logger:
public class ValuesController : ApiController
{
readonly ILogger _logger;
public ValuesController(ILogger logger)
{
_logger = logger;
}
// GET api/values
public IEnumerable<string> Get()
{
_logger.Log("GET api/values");
return new string[] { "value1", "value2" };
}
}
The console application:
class Program
{
static void Main(string[] args)
{
var configuration = new HttpSelfHostConfiguration("http://localhost:8080");
configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var builder = new ContainerBuilder();
// Register API controllers using assembly scanning.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// Register API controller dependencies.
builder.Register<ILogger>(c => new Logger()).SingleInstance();
var container = builder.Build();
// Set the dependency resolver implementation.
var resolver = new AutofacWebApiDependencyResolver(container);
configuration.DependencyResolver = resolver;
// Open the HTTP server and listen for requests.
using (var server = new HttpSelfHostServer(configuration))
{
server.OpenAsync().Wait();
Console.WriteLine("Hosting at http://localhost:8080/{controller}");
Console.ReadLine();
}
}
}
Hit the controller action using:
http://localhost:8080/api/values
Please test this out and let me know if you have any problems.
Not sure if this is what you want but you can create your own base controller and inject the IServerPackageRepository into it.
public class MyApiController : ApiController {
public IServerPackageRepository ServerPackageRepository { get; set; }
public MyApiController(IServerPackageRepository serverPackageRepository) {
ServerPackageRepository = serverPackageRepository;
}
}
Then, use this as your base controller:
public class ProductsController : MyApiController {
public ProductsController(IServerPackageRepository serverPackageRepository)
: base(serverPackageRepository) {
}
public IEnumerable<Product> Get() {
ServerPackageRepository.DoWork();
//...
}
}
An alternative would be to directly wire your dependency to the property like so:
var repo = new ServerPackageRepository(path: this.StoragePath);
builder.RegisterInstance(repo)
.SingleInstance();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly())
.WithProperty("Repository", repo)
.PropertiesAutowired();

Resources