How to add different MediaTypeFormatters for different controllers - asp.net-web-api

In the new ASP.NET Web Api you can hookup MediaTypeformatters to a controller like this:
protected void Application_Start()
{
var config = GlobalConfiguration.Configuration;
config.Formatters.Add(new ContactPngFormatter());
config.Formatters.Add(new VCardFormatter());
config.Routes.MapHttpRoute(
"Default", // Route name
"{controller}/{id}/{ext}", // URL with parameters
new { id = RouteParameter.Optional, ext = RouteParameter.Optional }
);
}
But this is a "global" configuration. What if I want a different set of MediaTypeFormatters for a specific controller. How would I create a specific configuration for that?

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

Web Api route configNot Working

I have these 2 configs:
config.Routes.MapHttpRoute(
name: "CustomRate1",
routeTemplate: "api/CustomRate/{action}/{idCustomRate}",
defaults: new { controller = "CustomRate" }
);
config.Routes.MapHttpRoute(
name: "CustomRate",
routeTemplate: "api/CustomRate/{action}/{idCountry}",
defaults: new { controller = "CustomRate" }
);
However, only one of them works at a time. If I put CustomRate before the route works, but CustomRate1 does not, the reverse also happens, if I put CustomRate1 on top CustomRate does not work. Any suggestions
Currently in my controller I have
[HttpPost()]
[HttpOptions]
public CustomRateListReturnType GetCustomRateListByCountry(long idCountry)
{
}
[HttpPost()]
[HttpOptions]
public BaseReturnType DesactivateCustomRate(long idCustomRate)
{
}
I am currently using these two calls
http://127.0.0.1/quotesystemserver/api/CustomRate/GetCustomRateListByCountry/5
and
http://127.0.0.1/quotesystemserver/api/CustomRate/DesactivateCustomRate/3
Ok What I did is as follows:-
My config is now as follows:-
config.Routes.MapHttpRoute(
name: "CustomRate1",
routeTemplate: "api/CustomRate/{action}/{id}",
defaults: new { controller = "CustomRate" }
);
My controller contains:-
[HttpPost()]
[HttpOptions]
public CustomRateReturnType GetCustomRateListByCountry(long id)
{
}
[HttpPost()]
[HttpOptions]
public BaseReturnType DesactivateCustomRate(long id)
{
}
Just have one route.
The placeholders {action} and {id} will take the values what you pass in the URL.
If you call http://127.0.0.1/quotesystemserver/api/CustomRate/GetCustomRateListByCountry/5
,the route will take action as GetCustomRateListByCountry and id as 5.
Whatever you give the name to the placeholder { } doesnot matter. It is just the placeholder name. The value comes from the URL.
use attribute routing, it gives more clearer picture of the custom routes for any person who is developing the application for rest you can use config routes:
Decorate your controller with
[RoutePrefix("api/CustomRate")]
And your action methods as follows
HttpPost()]
[HttpOptions]
[Route("GetCustomRateListByCountry/{idCountry:long}]
public CustomRateListReturnType GetCustomRateListByCountry(long? idCountry)
{
}
[HttpPost()]
[HttpOptions]
[Route("DesactivateCustomRate/{idCustomRate:long}]
public BaseReturnType DesactivateCustomRate(long? idCustomRate)
{
}
Add the below code in webapiconfig.cs
config.MapHttpAttributeRoutes();

ASP.NET MVC Areas routing not working with default route

I am trying to separate my MVC project into multiple areas. So i have 3 areas 1) crm 2)services 3) Web. I want PublicWeb to be my default one. that means it should be accessed like www.mysitename.com/mycontroller/myaction( no area name inbetween) and other two to be accessed with the area name (www.mysitename.com/crm/mycontroller/myaction). What routing/ Area configuration i should have ? I tried AreaRegistration.RegisterAllAreas(); and it works only for my default one (web). When i access the other 2, it threw 404 error.
I tried to register indidually like the below one
var area2reg = new crmAreaRegistration();
var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
area2reg.RegisterArea(area2context);
var area1reg = new webAreaRegistration();
var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
area1reg.RegisterArea(area1context);
Then my publicweb works. But when i access my forum it threw this error,
Multiple types were found that match the controller named 'home'. This can happen if the route that services this request ('crm/{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
My RegisterArea function for web is this
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"web_default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
and the one for crm is this
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"crm_default",
"crm/{controller}/{action}/{id}",
new { controller = "home", action = "Index", id = UrlParameter.Optional }
);
}
}
How do i handle this ?
From what I can see the area routes look fine. Did you update the default route in your Global.asax to send requests to the web area?
Something like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}",
new { area = "web", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I think Jonathan S's solution is definitely worth a try, but you might consider a different approach. That would be to put your web files in the default locations. The routing engine would not look in the Area's for those files when no Area is part of the request.

Unable to understand the Asp.net MVC routing

I have added the following route to my global.asax file :-
routes.MapRoute(
"Admin_Route",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "PriceCompare.Admin.Controllers" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "PriceCompare.Controllers" }
);
The admin controllers i.e. ManageCatsController, ManageBrandsController, etc. reside in PriceCompare.Admin.Controllers namespace and other general controllers reside in PriceCompare.Controllers namespace.
The problem is that i am able to visit all the controllers by adding Admin/ in front of them, irrespective of whether they are in PriceCompare.Admin.Controllers namespace.
Also, I am able to visit admin controllers directly without prefixing Admin/.
Why is this happening. Am i misunderstanding the routing behaviour.
You need to register your Admin area.
When I have registered routes for areas I've always done it like this:
Inside App/Areas/Admin folder create an AdminAreaRegistration.cs file with this in it...
using System.Web.Mvc;
namespace AppName.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin",
"Admin/{controller}/{action}/{id}",
new { controller="Home", action = "Index", id = UrlParameter.Optional },
new string[] { "AppName.Areas.Admin.Controllers" }
);
}
}
}
Now do this in Global.asax:
protected void Application_Start()
{
// Add this next line
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Add any other stuff (like IoC or whatever)
}
And only register your normal routes in RegisterRoutes in Global.asax, like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "AppName.Controllers" }
);
}
I would also recommend keeping the Area part inside your Admin namespace (so calling it PriceCompare.Areas.Admin.Controller in your case) as it will make life a lot easier later on when.
Please try this and let me know if it works :-)

RouteHandler vs ControllerFactory

new to asp.net mvc (using v3 + razor) and am wondering how to best solve a problem with creating dynamic routes based on a database. Essentially, the main site navigation will be entered into a database and I want to load them up as routes. i.e. - Load Category list from database, then append the routes to the routing engine if possible...
mysite.com/cars
mysite.com/televisions
mysite.com/computers
etc....
Each category after the slash comes from the db, but, there are regular entries like /about and /contactus that will not be in the database and have been statically entered in the global.asax... my question is:
For the dynamic database URLs should I use a custom RouteHandler or pehaps create a ControllerFactory that will match and handle the requests for the entries loaded from the database. Is it possible to have the DefaultControllerFactory handle the routing if my RouteHandler or CustomControllerFactory don't find the route in the list from the database? Thanks for any help, very first project with this so I'm not sure what the best route is ;) no pun intended...
Update:
Tried using a route constraint that pulls from the database but it conflicts with the default route now... here is my custom constraint and routes:
public class CategoryListConstraint : IRouteConstraint
{
public CategoryListConstraint()
{
var repo = new Repository<Topic>();
var cats = repo.All();
var values = new List<string>();
foreach (var c in cats)
{
values.Add(c.URI.Replace("/", "").Replace("?", ""));
}
this._values = values.ToArray<string>();
}
private string[] _values;
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
and here are the routes:
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Categories",
"{category}/{*values}",
new { controller = "Category", action = "List" },
new CategoryListConstraint()
);
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
The home page www.mysite.com loads using the Default route. All the URLs that match the constraint list are loaded by the category route... but if I have the www.mysite.com/admin or www.mysite.com/aboutus these are getting picked up by the Categories route even though the values are not in the constraint list. Confused...
What about something like this?
Categories controller:
public ActionResult List(string category)
{
var products = _repo.Get(category); // however you are getting your data
return View(products);
}
Routes
routers.MapRoute(
"About",
"About",
new { controller = "Home", action = "About" });
//... other static routes
routes.MapRoute(
"CategoriesList",
"{id}",
new { controller = "Categories", action = "List" },
new { id = #"\w+" });
The incoming URL is tested against each Route rule to see if it matches - and if a Route rule matches then that rule (and its associated RouteHandler) is the one that is used to process the request (and all subsequent rules are ignored). This means that you want to typically structure your routing Rules in a "most specific to least specific" order
source
Found the exact solution I was looking for. Code is below. I managed to avoid using Controller Factories or implementing a custom IRouteHandler by using extending the RouteBase class which worked perfectly and allows me to pass control down to the default mvc route is something specific isn't hit. BTW - constraints ended up not working properly as the broke the controllers associated with the default route (although the default route was getting hit)
public class CustomRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{*URI}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// implement caching here
var list = GetConstraintList();
// set your values dynamically here
routeData.Values["controller"] = "Category";
// or
routeData.Values.Add("action", "List");
// return the route, or null to have it passed to the next routing engine in the list
var url = Util.StripSlashOnFrontAndBack(httpContext.Request.Path.ToLower()).Split('/')[0];
if (list.Contains(url))
return routeData;
return null; // have another route handle the routing
}
protected List<string> GetConstraintList()
{
using (var repo = new RavenRepository<Topic>())
{
var tops = repo.Query().Where(x => x.Hidden == false).ToList()
.Select(x=>x.Name.ToLower());
List<string> list = new List<string>();
list.AddRange(tops);
repo.Dispose();
return list ?? new List<string>();
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
Then my register routes method looks like so:
Routes.Clear();
// Set Defaults
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add(new App.Helpers.CustomRoutingEngine());
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Resources