I'm pretty new to .NET Core. I'm trying to set up a little MVC application. Where i implemented a controller with a defined Route.
[Route("api/ota")]
public class OTAController : ControllerBase
{
[HttpPost]
public async Task<ContentResult> EndPoint([FromBody] object otaHotelRatePlanNotifRQ)
{
Console.WriteLine("Something is posted");
...
for this controller i implemented a custom inputformatter and registered it in the Startup.cs works fine so far.
services.AddMvc(options => {
options.InputFormatters.Insert(0, new RawRequestBodyInputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
But now this inputformatter is applied for any controller and specified route.
Is there any way to apply the formatter just for a specified controller/route.
add your inputformatter as the first formatter InputFormatters.Insert(0,new StringRawRequestBodyFormatter()) then in this formatter in CanRead method check if the parameter that is being bound has a custom attribute you specify alongside FromBody
public override Boolean CanRead(InputFormatterContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var contentType = context.HttpContext.Request.ContentType;
if (supportedMimes.Contains(contentType) &&
context.Metadata is DefaultModelMetadata mt &&
mt.Attributes.ParameterAttributes.Any(a=>a.GetType().Equals(typeof(RawJsonAttribute))))
return true;
else
return false;
}
Controller action:public IActionResult BeginExportToFile([RawJson,FromBody] string expSvcConfigJsonStr)
So in simple terms this formatter will only be used for the supported mimes and for parameters that have a custom attribute.
Hope it helps.
Yes this can be in the Startup.cs by adding a new route in the config method, you should have something like this by default you need to add a new one for the controller that you want:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Note: The order matters.
Related
I have a simple ASP.Net Core 3.1 Razor Pages project. In the Index.cshtml I have changed #page to #page "{handler?}" so I can use routing-based handler methods. That works fine. The issue is, now this page becomes a catch-all route for any url. For example, I can go to http://mysite/foo and it will not give a 404 anymore. How can I make it return a 404 if the handler method doesn't exist?
I dont know if this is best solution:
public class IsValidPageHandler : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
if (!routeContext.RouteData.Values.ContainsKey("handler")
|| routeContext.RouteData.Values["handler"] == null
|| ((CompiledPageActionDescriptor) action).HandlerMethods.Any(f=>routeContext.RouteData.Values["handler"].ToString().Equals(f.Name, StringComparison.OrdinalIgnoreCase)))
return true;
else
return false;
}
}
and then
...
endpoints.MapRazorPages();
var actions = app.ApplicationServices.GetService<IActionDescriptorCollectionProvider>();
foreach (var item in actions.ActionDescriptors.Items.Where(f => f.AttributeRouteInfo != null && f.AttributeRouteInfo.Template.Contains("{handler?}")).ToList())
item.ActionConstraints.Add(new IsValidPageHandler());
...
other solution create a Filter and add Filter but that one happens later in a pipe. within you have context with context.HandlerMethod and RouteData which you can compare similar to above
public class CustomRazorPageFilter : IAsyncPageFilter
{
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
...
}
...
I have a problem with the behaviour of Url.Action();
I have a webapi where all controllers require explicit route prefix attribute and all actions require a route attribute.
I register my routes in the WebApiConfig.cs
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
I have currently commented out the line below, but (because) it did not change the incorrect behaviour:
//config.Routes.MapHttpRoute(name: "DefaultApi",
//routeTemplate: "api/v{version:apiVersion}/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional});
My controllers look as follows:
[RoutePrefix("api/v{version:apiVersion}/programs")]
public class ProgramsController : ApiController
{
[HttpGet, Route("{telemetryKey}/versions/latest")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
}
I expect that '#Url.Action("GetLatestVersionInfo", "Programs", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })'
should return /api/v1/programs/43808405-afca-4abb-a92a-519489d62290/versions/latest
however, I get /Programs/GetLatestVersionInfo?telemetryKey=43808405-afca-4abb-a92a-519489d62290 instead. So, my routeprefix and route attributes are ignored.
Swagger correctly discovers my routes and I can validate that requests to the expected routes work OK - it's only the Url.Action() that is confused.
What can be wrong...?
Well, it seems there were a few things wrong.
Wrong helper:
I should be using the Url.HttpRouteUrl for generating API links from a razor view (Url.Link is for generating link from within API controllers)
Conflict with aspnet-api-versioning library
For some reason (perhaps a bug?) the prefix that I have on the controller (apiVersion variable) breaks the URL helper mechanism.
For now, I have ditched the aspnet-api-versioning library, but created an issue on their github repo, in case its a bug.
Since I really hate the idea of creating and maintaing magic strings, so I took the following approach - each controller has a public static class which contains const values for the route names:
[RoutePrefix("api/v1/developers")]
public class DevelopersController : ApiController
{
[HttpGet, Route("{developerId}/programs", Name = Routes.GetPrograms)]
public async Task<IEnumerable<Program>> GetPrograms(Guid developerId){}
public static class Routes
{
public const string GetPrograms = nameof(DevelopersController) +"."+ nameof(DevelopersController.GetPrograms);
}
}
Now that can be used from a razor controller in a simple and relatively safe manner:
#Url.HttpRouteUrl(DevelopersController.Routes.GetPrograms, new { developerId = /* uniquest of guids */})
A bit better than magic strings. I've also added a bunch of unit tests for controllers where I validate that each route is unique and proper and that the routes class only contains routes for the action it contains.
Try the following:
Name your route:
[HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
Use Url.Link method:
#Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })
I have a bit of problem
I have an MVC site that is working just fine.
I wanted to have SEO friendly urls so I created custom routing that looks like this
{section}/{group}/{manufacturer}
since it has the same number of fields as the default route
{controller}/{action}/{id}
i created in loop in RouteConfig.cs that goes to database and loops Sections table and creates custom routes like
FOOD/{group}/{manufacturer}
SPORT/{group}/{manufacturer}
CARS/{group}/{manufacturer}
and point it to the Sections controller.
It works just fine. The problem I have is that now i need to rename FOOD to FRESH-FOOD and i want to keep old links to FOOD working.
What would you recommend ? How can I solve this ?
Is there a better way to do route Section instead of custom routes ?
Sounds like this might be a good case for a custom route handler. In the ProcessRequest method below you can check for controller name of "FOOD" and redirect to "FRESH-FOOD" like this:
public class CustomRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomHttpHandler();
}
}
public class CustomHttpHandler : IHttpHandler
{
public bool IsReusable
{
get
{
return true;
}
}
public void ProcessRequest(HttpContext context)
{
var routeValues = context.Request.RequestContext.RouteData.Values;
var controllerName = context.Request.RequestContext.RouteData.GetRequiredString("controller");
if (controllerName.ToLower() == "food") controllerName = "FRESH-FOOD";
var controller = ControllerBuilder.Current.GetControllerFactory().
CreateController(context.Request.RequestContext, controllerName);
if (controller != null)
{
controller.Execute(context.Request.RequestContext);
}
}
}
When you create your custom routes use the Route constructor overload that takes an IRouteHandler
I've a self host Web API with 2 controllers:
For controller 1, I need default DataContractSerializer (I'm exposing EF 5 POCO)
For controller 2, I need XmlFormatter with parameter UseXmlSerializer set to true (I'm exposing an XmlDocument)
I've tried to set formatters during controller initialization, but the configuration seems to be global, affecting all controllers:
public class CustomConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings settings,
HttpControllerDescriptor descriptor)
{
settings.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
How can I solve this?
You were very much on the right track. But you need to initallise a new instance of the XmlMediaTypeFormatter in your config attributes otherwise you will affect the global reference.
As you know, you need to create 2 attributes based on the IControllerConfiguration interface.
public class Controller1ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter {UseXmlSerializer = true};
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
public class Controller2ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter();
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
Then decorate your controllers with the relevant attribute
[Controller1ConfigAttribute]
public class Controller1Controller : ApiController
{
[Controller2ConfigAttribute]
public class Controller2Controller : ApiController
{
Configuration:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Insert(0, new CustomXmlMediaTypeFormatter());
The Custom formatter:
public class CustomXmlMediaTypeFormatter : XmlMediaTypeFormatter
{
public CustomXmlMediaTypeFormatter()
{
UseXmlSerializer = true;
}
}
This seems to work, ok not so elegant.
Removing default Xml Formatter does not work,
so I concluded that the framework is somehow still using it.
Mark Jones' answer has a big downside: By clearing all formatters it is not possible to request different ContentTypes and make use of the relevant formatter.
A better way to enable the XMLSerializer per Controller is to replace the default formatter.
public class UseXMLSerializerAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
// Find default XMLFormatter
var xmlFormatter = controllerSettings.Formatters.FirstOrDefault(c => c.SupportedMediaTypes.Any(x => x.MediaType == "application/xml"));
if (xmlFormatter != null)
{
// Remove default formatter
controllerSettings.Formatters.Remove(xmlFormatter);
}
// Add new XMLFormatter which uses XmlSerializer
controllerSettings.Formatters.Add(new XmlMediaTypeFormatter { UseXmlSerializer = true });
}
}
And use it like this:
[UseXMLSerializer]
public TestController : ApiController
{
//Actions
}
I think you could write a custom ActionFilterAttribute.
In OnActionExecuting, store away the original values in the HttpContext and then in OnActionExecuted, restore the original values.
the controllers actions themselves should not be concerned with how the data is serialized. yo should be able to request the data and any format necessary the operation to retrieve the data would be the same.
by default web api serialized to json objects. however if you set the content type of the request to xml is should return the same result, but formatted as xml instead of json.
I am creating an ActionResult in ASP.Net MVC to serve images. With Session state enabled, IIS will only handle one request at a time from the same user. (This is true not just in MVC.)
Therefore, on a page with multiple images calling back to this Action, only one image request can be handled at a time. It's synchronous.
I'd like this image Action to be asynchronous -- I'd like multiple image requests to each execute without needing the previous one to complete. (If the images were just static files, IIS would serve them up this way.)
So, I'd like to disable Session just for calls to that Action, or to specify that certain requests do not have Session state. Anyone know how this is done in MVC? Thanks!
If anyone is in the situation I was in, where your image controller actually needs read only access to the session, you can put the SessionState attribute on your controller
[SessionState(SessionStateBehavior.ReadOnly)]
See http://msdn.microsoft.com/en-us/library/system.web.mvc.sessionstateattribute.aspx for more info.
Thanks to https://stackoverflow.com/a/4235006/372926
Rather than implementing an action filter for this, why don't you implement a RouteHandler?
Here's the deal - IRouteHandler has one method - GetHttpHandler. When you make an ASP.Net MVC request to a controller, by default the routing engine handles the request by creating a new instance of MvcRouteHandler, which returns an MvcHandler. MvcHandler is an implementation of IHttpHandler which is marked with the (surprise!) IRequiresSessionState interface. This is why a normal request uses Session.
If you follow my blog post on how to implement a custom RouteHandler (instead of using MvcRouteHandler) for serving up images - you can skip returning a session-tagged IHttpHandler.
This should free IIS from imposing synchronicity on you. It would also likely be more performant because it's skipping all the layers of the MVC code dealing with filters.
I also came across the same problem and after doing R&D this link worked for me
Reference:
https://techatfingers.wordpress.com/2016/06/14/session-state-on-action/
Create custom Attribute
Override the “GetControllerSessionBehavior” method present in class DefaultControllerFactory.
Register it in global.aspx
1> Create custom Attribute
public sealed class ActionSessionStateAttribute : Attribute
{
public SessionStateBehavior SessionBehavior { get; private set; }
public ActionSessionStateAttribute(SessionStateBehavior sessionBehavior)
{
SessionBehavior = sessioBehavior;
}
}
2. Override
public class SessionControllerFactory : DefaultControllerFactory
{
protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return SessionStateBehavior.Default;
var actionName = requestContext.RouteData.Values["action"].ToString();
Type typeOfRequest=requestContext.HttpContext.Request.RequestType.ToLower() =="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);
// [Line1]
var cntMethods = controllerType.GetMethods()
.Where(m =>
m.Name == actionName &&
( ( typeOfRequest == typeof(HttpPostAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
)
||
( typeOfRequest == typeof(HttpGetAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
)
)
);
MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;
if (actionMethodInfo != null)
{
var sessionStateAttr = actionMethodInfo.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
.OfType<ActionSessionStateAttribute>()
.FirstOrDefault();
if (sessionStateAttr != null)
{
return sessionStateAttr.Behavior;
}
}
return base.GetControllerSessionBehavior(requestContext, controllerType);
}
3. Register class in Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// --- other code ---
ControllerBuilder.Current.SetControllerFactory(typeof(SessionControllerFactory));
}
}
Try serving the images from another domain. So something like images.mysite.com.
This will provide you two benefits: One, sessions are tracked by a cookie, so images.mysite.com won't have the cookie. Two, it will give you an additional two concurrent requests to retrieve images.
Have you considered setting up a HttpHandler to serve up your images?
SessionState attribute is quite helpful if u use mvc3. How to achieve this with mvc2 needs a little more coding.
Idea is to tell the asp.net that specific request wont use session object.
So, Create a custom route handler for specific requests
public class CustomRouteHandler : IRouteHandler
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
return new MvcHandler(requestContext);
}
}
SessionStateBehavior enum has 4 members, you should use "disabled" or "readonly" modes to get async behavior.
After creating this custom route handler, be sure that your specific requests goes through this handler. This can be done via defining new routes at Global.asax
routes.Add("Default", new Route(
"{controller}/{action}",
new RouteValueDictionary(new { controller = "Home", action = "Index"}),
new CustomRouteHandler()
));
Adding this route makes all your requests to be handled by your custom route handler class. You can make it specific by defining different routes.
Change DefaultCOntrollerFactory to custom ControllerFactory class. Default Controller.TempDataProvider use SessionStateTempDataProvider. you can change it.
1.Set web.config/system.web/sessionState:mode="Off".
2.create DictionaryTempDataProvider class.
public class DictionaryTempDataProvider : ITempDataProvider
{
public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
return new Dictionary<string, object>();
}
public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
}
}
3.Create DictionaryTempDataControllerFactory
public class DictionaryTempDataControllerFactory : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
var controller = base.CreateController(requestContext, controllerName) as Controller;
if (controller!=null)
controller.TempDataProvider = new DictionaryTempDataProvider();
return controller;
}
}
4.In global.asax.cs Apprication_Start event set DictionaryTempDataControllerFactory.
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(
new DictionaryTempDataControllerFactory()
);
}
On our server, IIS doesn't even know about sessions - it's the ASP.NET stack that handles one request per session at a time. Static files, like images, are never affected.
Is it possible that your ASP.NET app is serving the files instead of IIS?
Create new Controller
Decorate controler with [SessionState(SessionStateBehavior.Disabled)]
Refactor code you want seesion stated disabled for to that controller
I would to share my solution for disable ASP.NET Session for an specific request (in my case, a WCF Service) using an HttpModule:
public class AspNetSessionFilterModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostMapRequestHandler += OnPostMapRequestHandler;
}
private void OnPostMapRequestHandler(object sender, EventArgs e)
{
var context = (sender as HttpApplication).Context;
DisableSessionForSomeRequests(context);
}
private void DisableSessionForSomeRequests(HttpContext context)
{
if ("~/Services/MyService.svc".Equals(context.Request.AppRelativeCurrentExecutionFilePath, StringComparison.InvariantCultureIgnoreCase))
{
context.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
}
}
public void Dispose()
{ }
}