I am trying to read the appsettings.json file with the strongly type class and pass that as a parameter to controller. However its not working. Here is the code.
appsettings.json file:
{
"AppSettings": {
"ApplicationName": "TestApp"
}
}
AppSettings Class:
public class AppSettings
{
public string ApplicationName { get; set; }
}
Injecting in Startup Class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
Controller:
public class ValuesController : Controller
{
private readonly IOptions<AppSettings> _appsettings;
public ValuesController(IOptions<AppSettings> appsettings)
{
_appsettings = appsettings;
}
[HttpGet]
public string Get()
{
return _appsettings.Options.ApplicationName;
}
}
The startup program is successfully getting executed. However, the controller constructor or default get method is not called.
It is working, If I remove the (IOptions appsettings) from the controller constructor.
What's wrong with my code.
IOptions.Options was renamed to IOptions.Value in beta8. See this question.
Changing your Get action from:
return _appsettings.Options.ApplicationName;
to:
return _appsettings.Value.ApplicationName;
should fix that issue.
UPDATE 3/8/2016
The other issue I see here is that you're calling the Get action the "default" action, but the default routing in ASP.Net Core looks for an Index action on the controller.
You can configure the route in Startup.cs to look for a Get action by default instead of an Index action by modifying the Configure function to include something like this:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Get}/{id?}");
});
The default implmentation uses the template template: "{controller=Home}/{action=Index}/{id?}" which is why it looks for an Index action.
Your other options would be to change your Get function to an Index function, explicitly specify the Get action in the url when you visit the site (ex. http://localhost/Values/Get), or specify the action name for the Get method in your controller like this:
[HttpGet]
[ActionName("Index")]
public string Get()
{
return _appsettings.Value.ApplicationName;
}
Related
I'm using https://github.com/ardalis/ApiEndpoints (one action per controller) for my project, and I run into issue that [Route("[controller]")] is not really suitable for me since controllers look like this:
I need something more like [Route("[namespace]")], but that's not supported in ASP.NET Core.
Is there a way, to add custom route token resolution in Startup.cs?
My solutions so far:
Hardcode routes
Create custom attribute that would contain route with custom tokens, and source generator that would resolve custom tokens and generate Route attribute.
Big thanks to #Kahbazi for pointing me in the right direction!
Here's what I came up with:
private class CustomRouteToken : IApplicationModelConvention
{
private readonly string _tokenRegex;
private readonly Func<ControllerModel, string?> _valueGenerator;
public CustomRouteToken(string tokenName, Func<ControllerModel, string?> valueGenerator)
{
_tokenRegex = $#"(\[{tokenName}])(?<!\[\1(?=]))";
_valueGenerator = valueGenerator;
}
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
string? tokenValue = _valueGenerator(controller);
UpdateSelectors(controller.Selectors, tokenValue);
UpdateSelectors(controller.Actions.SelectMany(a => a.Selectors), tokenValue);
}
}
private void UpdateSelectors(IEnumerable<SelectorModel> selectors, string? tokenValue)
{
foreach (var selector in selectors.Where(s => s.AttributeRouteModel != null))
{
selector.AttributeRouteModel.Template = InsertTokenValue(selector.AttributeRouteModel.Template, tokenValue);
selector.AttributeRouteModel.Name = InsertTokenValue(selector.AttributeRouteModel.Name, tokenValue);
}
}
private string? InsertTokenValue(string? template, string? tokenValue)
{
if (template is null)
{
return template;
}
return Regex.Replace(template, _tokenRegex, tokenValue);
}
}
Configure the token in Startup.cs (this can be wrapped in an extension method):
services.AddControllers(options => options.Conventions.Add(
new CustomRouteToken(
"namespace",
c => c.ControllerType.Namespace?.Split('.').Last()
));
After that custom token can be used for routing:
[ApiController]
[Route("api/[namespace]")]
public class Create : ControllerBase {}
[ApiController]
public class Get : ControllerBase
{
[HttpGet("api/[namespace]/{id}", Name = "[namespace]_[controller]")]
public ActionResult Handle(int id) {}
}
You can achieve that with implementing IApplicationModelConvention. more info on here : Custom routing convention
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/') + "/[controller]}"
};
}
}
}
And then add it in startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
Areas are exactly what you need. As mentioned in .net core documentation:
Areas are an ASP.NET feature used to organize related functionality
into a group as a separate:
Namespace for routing.
Folder structure for views and Razor Pages.
To use area routes you need only to add them to the startup.
Like this:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
After that the route will follow your folder structure inside the Areas folder.
For more information visit the Areas in ASP.NET Core documentation.
Our app loads controllers in external assemblies we call packages. I want to create a route that routes to a package using URLs like package/BillingPackage/Invoice rather than api/BillingPackage/Invoice. Here is what I have done:
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseEndpointRouting()
.UseMvc(routes =>
{
routes.MapRoute(
name: "package",
template: "package/{package}/{controller}/{id?}");
routes.MapRoute("api", "api/{controller}/{action=Get}/{id?}");
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseStaticFiles();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var source = new PackageAssemblySource(Configuration);
var packageAssemblies = source.Load();
var builder = new ContainerBuilder();
builder.RegisterModule(new WebApiModule(packageAssemblies));
services
.AddMvc()
.ConfigureApplicationPartManager(manager =>
{
// Add controllers and parts from package assemblies.
foreach (var assembly in packageAssemblies)
{
manager.ApplicationParts.Add(new AssemblyPart(assembly));
}
});
.AddControllersAsServices() // Now that AssemblyParts are loaded.
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);;
builder.Populate(services);
ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
Then I define a controller like this:
[Route("package/BillingPackage/[controller]", Name = "Invoice")]
public class InvoiceController : ControllerBase
{
[HttpGet()]
public ActionResult<Invoice> Get()
{
return new SampleInvoice();
}
}
Even with all that, package/BillingPackage/Invoice yields a 404 whereas api/BillingPackage/Invoice does not. How do I get my WebApi to serve endpoints from package rather than api?
You are probably experiencing route conflict with template: "package/{package}/{controller}/{id?}".
If using attribute routing on the controller then remove that convention-based route
To get the desired behavior, you would need to include a template parameter [Route("package/{package}/[controller]", Name = "Invoice")] along with a method/action argument public ActionResult<Invoice> Get(string package) which will be populated from the matched value from the URL.
For example
[Route("package/{package}/[controller]", Name = "Invoice")]
public class InvoiceController : ControllerBase {
//GET package/BillingPackage/Invoice
[HttpGet()]
public ActionResult<Invoice> Get(string package) {
return new SampleInvoice();
}
}
Reference Routing to controller actions in ASP.NET Core
I know this has been asked a lot of times but nobody really wraps it up.
I'm currently using a .NET Core Backend which has a web api controller.
I know that there are multiple ways of handling the routing in .NET Core e.g.
Using Constraints
[HttpGet("{id:int}")]
public string GetById(int id)
{
return "item " + id;
}
Creating Default Routes
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Building RESTful Routes
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"hello", "world!"};
}
// POST api/values
[HttpPost]
public void PostCreate([FromBody] string value)
{
}
}
and a couple more options.
However I just can't figure out how to pass multiple parameters in one route.
In my case I have to update two different objects at the same time and have to use either an HTTP Patch or HTTP Put request.
I could go with something like api/ExampleName/Id but what I need is something like api/ExampleName/ObjectOneID&ObjectTwoID
Does anyone know how to build something like this?
I thought about creating a default route in the startup file and configure it right there. However I suppose it wants a specific syntax in order to read multiple parameters
You can use parameters in the URI's themselves - like http://myapi.com/api/{controller}/{action}?id=someId&anotherId=someOtherId
Your action method would then be like
public void PostCreate([FromUri]Guid someId, [FromUri]Guid anotherId)
[HttpPatch]
public async Task<string> Patch([FromBody] Model request)
where Model is
class Model
{
public int ObjectOneID {get; set;}
public int ObjectTwoID {get; set;}
}
to use it just create request with body that contains ObjectOneID and ObjectTwoID like
PATCH api/ExampleName
Content-Type: application/json
{
"ObjectOneID":1,
"ObjectTwoID":2
}
In this scenario I suggest to use POST/PUT with model
[Route("dummy")]
[HttpPost]
public async Task<IHttpActionResult> Dummy(Foo req)
{
//logic
}
public class Foo
{
public CustomObjectBody ObjectOneID {get;set;}
public CustomObjectBody ObjectTwoID {get;set;}
}
public class CustomObjectBody
{
public int Property1 {get;set;}
public int Property2 {get;set;}
}
If we ask the MVC framework to generate URLs for us, for example by using UrlHelper inside a controller, the route segments in the generated URL will be upper case.
[Route("[controller]")]
public class PeopleController : Controller
{
[HttpGet]
public IActionResult Get()
{
var url = this.Url.Action("Get", "People"); // Returns "/People"
...
}
}
How is it possible to tell MVC to generate lower case routes instead, so in the above example, return "/people"?
It's simple to achieve this, in the ConfigureServices method of our Startup class we just have to configure routing by setting the LowerCaseUrls property to true.
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting(routeOptions => routeOptions.LowercaseUrls = true);
...
}
}
I've recently asked a few questions about the best way to create a web api which utilises the same url as my main mvc site. I deduced the best way was to make the necessary changes to my MVC site to include web api and the necessary routing.
I have mainly followed How to add Web API to an existing ASP.NET MVC 4 Web Application project? but I have run into problems. The code compiles fine and it is clearly looking for the route but I get the error:
No HTTP resource was found that matches the request URI 'http://localhost:2242/api/value'.
No type was found that matches the controller named 'value'.
My WebApiConfig:
class WebApiConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
}
}
my global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer<ApplicationDbContext>(null);
}
}
my api controller:
public class ValuesController1 : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
Other posts have corroborated that this is a correct and working setup...I created a separate webapi project to compare and this is all correct routing wise apparently. It would be far preferable to build this into my MVC website, does anyone have any ideas? This poster No type was found that matches controller had the same problem and the solution he found was to copy everything into a new project....that really isn't something I want to do/see why I should need to do.
I think it is because of your Controller's name : ValuesController1
A controller has to be suffixed by "Controller", the 1 may be the cause of your issue.
The name of the controller ValuesController1 doesn't match convention - in order for the default route to match /api/value based on the default convention set in your call to configuration.Routes.MapHttpRoute(...), the controller should be called ValueController:
public class ValueController : ApiController
{
public IEnumerable<string> Get()
// ...
However, if you intend to deviate from the configured convention, you can apply RouteAttribute and RoutePrefixAttribute in conjunction with the Http* verb attributes to customise controller and method routes, e.g.
[RoutePrefix("api/Foo")]
public class ValuesController : ApiController
{
// get api/Foo/value
[HttpGet]
[Route("value")]
public IEnumerable<string> NameDoesntMatter()
{
return new string[] { "value1", "value2" };
}
// get api/Foo/value/123
[HttpGet]
[Route("value/{id}")]
public string AnotherRandomName(int id)
{
return "value";
}
Before using the RouteAttribute you will need to add the following to your WebApiConfig.Register(HttpConfiguration config):
config.MapHttpAttributeRoutes();
Even with the routing attributes, note however that the controller class name still needs to end with the suffix Controller, i.e. cannot end in the suffix 1. It is surprisingly difficult to alter this convention.