I'm working with MVC 4 Web API and I have this dummy ValueProvider:
DummyValueProvider.cs
class DummyValueProvider : IValueProvider
{
public DummyValueProvider()
{
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
return new ValueProviderResult("testing", "testing", System.Globalization.CultureInfo.InvariantCulture);
}
}
class DummyValueProviderFactory : System.Web.Http.ValueProviders.ValueProviderFactory
{
public override IValueProvider GetValueProvider(System.Web.Http.Controllers.HttpActionContext actionContext)
{
return new DummyValueProvider();
}
}
This ValueProvider should return true for any key asked, so it will always supply a value to the model binder when it needs. The ValueProvider is registered in the WebApiConfig like this:
WebApiConfig.cs
config.Services.Add(typeof(ValueProviderFactory), new DummyValueProviderFactory());
The code compiles and runs fine.
I also have this action in the Account API controller:
AccountController.cs
public HttpResponseMessage Register(string foo) { ... }
The action gets called fine when I call it like below:
/register?foo=bar
And foo is filled with bar as expected; but if I call:
/register
The server returns 404 with the message No HTTP resource was found that matches the request URI 'http://localhost:14459/register'.
Also, I put breakpoints inside methods ContainsPrefix() and GetValue(), but they never get triggered.
What am I doing wrong? Shouldn't DummyValueProvider be providing the value testing to parameter foo?
Try this
public HttpResponseMessage Get([ValueProvider(typeof(DummyValueProviderFactory))] string foo) {... }
I higly suggest you to read this recent article to customize Web Api Binding.
Update:
After reading the article the OP was able to discover the solution. It was that using the parameter attribute [ModelBinder] was required for it to work. This was because unless the parameter is annotated, [FromUri] is assumed. Once annotated with [ModelBinder] the registered handlers are executed.
Related
I have wrote a Middleware which checks if Authorization Token is included in the header and based on that request are executed or returns error if token is missing. Now it is working fine for other Controllers.
But What should I do for Login/Registration Controller which don't required Authorization headers. How can I configure my Middleware to ignore these.
Current Implementation of MiddleWare to Check Headers for Authorization Token.
public class AuthorizationHeaderValidator
{
private readonly RequestDelegate _next;
private readonly ILogger<AuthorizationHeaderValidator> _logger;
public AuthorizationHeaderValidator(RequestDelegate next, ILogger<AuthorizationHeaderValidator> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
StringValues authorizationHeader;
Console.WriteLine(context.Request.Path.Value);
if (context.Request.Headers.TryGetValue("Authorization", out authorizationHeader))
{
await _next(context);
}
else
{
_logger.LogError("Request Failed: Authorization Header missing!!!");
context.Response.StatusCode = 403;
var failureResponse = new FailureResponseModel()
{
Result = false,
ResultDetails = "Authorization header not present in request",
Uri = context.Request.Path.ToUriComponent().ToString(),
Timestamp = DateTime.Now.ToString("s", CultureInfo.InvariantCulture),
Error = new Error()
{
Code = 108,
Description = "Authorization header not present in request",
Resolve = "Send Request with authorization header to avoid this error."
}
};
string responseString = JsonConvert.SerializeObject(failureResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(responseString);
return;
}
}
}
This is not a complete answer but only directions. Please post your code once you finish this task for next generations.
It seems you need a Filter and not Middlware as Middleware don't have access to rout data. Create new authorization filter by inheriting from Attribute and implementing IAuthorizationFilter or IAsyncAuthorizationFilter. There is only one method to implement
public void OnAuthorization(AuthorizationFilterContext context)
{
}
or
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
}
Decorate controllers and/or actions that you want to exclude from this logic with AllowAnonymousAttribute. Inside your OnAuthorization method check if current action or controller has AllowAnonymousAttribute and if it is return without setting Result on AuthorizationFilterContext. Otherwise execute the logic from you original Middleware and set Result property. Setting Result will short-circuit the remainder of the filter pipeline.
Then register your filter globally:
services.AddMvc(options =>
{
options.Filters.Add(new CustomAuthorizeFilter());
});
Not sure why you need middleware to validate if the Authorization header is present. It's difficult to exclude the controllers this way as all requests will go through this middleware before they hit the MVC pipeline.
[Authorize] attribute will do the job for you, given that you have some form of authentication integrated. If you need to exclude the controllers which don't require authorization, you can simply add [AllowAnonymous] at the controller level or at the action method level. Please see the code snippet below from the Microsoft Docs
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
public ActionResult Logout()
{
}
}
If you must use a middleware, you can consider using it as an MVC filter, which means that it will be scoped to the MVC pipeline. For more details, please see this link. However, that will still not solve the problem to exclude the controllers without adding some convoluted logic, which can be quite complicated.
I have solved my problem by Implementing PipeLine
public class AuthorizationMiddlewarePipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseMiddleware<AuthorizationHeaderValidator>();
}
}
And than I am using it like this on either Controller Scope or Method scope
[MiddlewareFilter(typeof(AuthorizationMiddlewarePipeline))]
I am asking this because after long time searching I haven't found a good answer on this yet...
Here is what I want:
Example: I have a domain model "JobPosting" which a user should be able to change state to published, if it is still a draft. Before publishing I must not only validate the model properties I must also validate many different requirements regarding the user account, it's registered company etc. All this validation logic is put into a service layer. So far so good...
This is how my service layer looks like:
public IValidationResult ValidatePublish(JobPosting jobPosting){
...
}
public void Publish(JobPosting jobPosting){
jobPosting.State = JobPostingState.Published;
...
}
Any my controller:
public ActionResult Publish(PublishViewModel model){
...
var validationResult = _jobService.ValidatePublish(jobPosting);
if(validationResult.Success){
_jobService.Publish(jobPosting);
...
}
...
}
And here now my questions:
I want to be able to call the ValidatePublish from the controller to show validation errors in the view. However I must never be able to publish a job when validation fails.
So to have my code more robust I added a second validation check in my Publish method in service layer:
public void Publish(JobPosting jobPosting){
if(ValidatePublish(jobPosting).Success){
jobPosting.State = JobPostingState.Published;
...
}
}
but I have not such a good feeling with this approach because now I am calling the validation twice when validation is OK during each controller publish request.
What do you think. Is the second call to much? Is there a better approach?
I am asking because my whole application looks like that and if I would ever forget a validation call in controller I might end up with an not allowed domain model state in database. That's why I added the second validation check in each service method.
Thanks in advance for your thoughts on this!!!
One quick solution might be to have the Publisher class require the JobPosting and IValidationResult objects as arguments.
public void Publish(JobPosting jobPosting, IValidationResult validation)
{
if (validation.IsValid)
{
jobPosting.State = JobPostingState.Published;
// other work here...
}
}
Your Controller can then call the Validator, receive an IValidationResult and pass that back to the presentation layer if needed. Otherwise pass on to Publisher
public ActionResult Publish(PublishViewModel model)
{
var validationResult = _jobService.ValidatePublish(jobPosting);
if(validationResult.Success) _jobService.Publish(jobPosting, validationResult);
else return View("error", validationResult);
}
Edit:
A cleaner solution may be to have the Publisher class return a PublishAttempt result.
public class PublishAttempt : IValidationResult
{
public enum AttemptOutcome {get; set;}
}
public ActionResult Publish(PublishViewModel model)
{
var attempt = _jobService.Publish(jobPosting);
if (attempt.Success) return View("success");
else return View("error", attempt.ValidationResults);
}
The following just came into my mind... what do you think:
I change my service method to:
public IValidationResult Publish(JobPosting jobPosting, bool validateOnly = false){
var validationResult = ValidatePublish(jobPosting);
if(validateOnly) return validationResult;
jobPosting.State = JobPostingState.Published;
...
return validationResult;
}
And then in controller I always call only the Publish method and not the extra ValidatePublish anymore:
public ActionResult Publish(PublishViewModel model)
{
var validationResult = _jobService.Publish(jobPosting);
if(!validationResult.Success) return View("error", validationResult);
}
And when I need only simple validation I do
var validationResult = _jobService.Publish(jobPosting, true);
Is this okey to do it like that?
Or is it not good looking if a normal service call returns IValidationResult?
I have a method on an ApiController that looks like this:
public IEnumerable<Items> GetSlideSets() {
IServiceClass serviceClass = new ServiceClass();
//...
Yes, I am aware that this is not good design but I'm addressing this issue in a different iteration.
At a certain point in my application I need to call this functionality from within the project itself so I thought I could simply reuse the controller (and why not, I can pluck it out of my IoC container). The only problem is that in this case, I need to inject my own implementation of IServiceClass, easy enough:
public IEnumerable<Items> GetSlideSets(IServiceClass serviceClass = null) {
serviceClass = serviceClass ?? new ServiceClass();
//...
Except now I am getting errors when calling this via a regular Api call Optionalparameter 'serviceClass' is not supported by FormatterParameterBinding.
I know that there are various attributes that control bindings. Is there one that I can put on the parameter to say it shouldn't bind.
Like others have mentioned, it's probably a better idea to inject the dependency in the constructor.
But if you really must avoid binding an action parameter, there isn't a built-in attribute but you can create one pretty easily. Here's what it could look like:
public class DontBindAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new DontBindParameterBinding(parameter);
}
private class DontBindParameterBinding : HttpParameterBinding
{
public DontBindParameterBinding(HttpParameterDescriptor parameter) : base(parameter)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
actionContext.ActionArguments.Add(Descriptor.ParameterName, Descriptor.DefaultValue);
var completedTaskSource = new TaskCompletionSource<object>();
completedTaskSource.SetResult(null);
return completedTaskSource.Task;
}
}
}
You just need to apply the attribute to the parameter afterwards:
public IEnumerable<Items> GetSlideSets([DontBind] IServiceClass serviceClass = null)
I'm trying to figure out the madness behind the Web API routing.
When I try to post data like this:
curl -v -d "test" http://localhost:8088/services/SendData
I get a 404, and the following error message:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:8088/services/SendData'.","MessageDetail":"No action was found on the controller 'Test' that matches the request."}
Here is the code for my test server.
public class TestController : ApiController
{
[HttpPost]
public void SendData(string data)
{
Console.WriteLine(data);
}
}
class Program
{
static void Main(string[] args)
{
var config = new HttpSelfHostConfiguration("http://localhost:8088");
config.Routes.MapHttpRoute(
name: "API Default",
routeTemplate:"services/SendData",
defaults: new { controller = "Test", action = "SendData"},
constraints: null);
using (var server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
}
More generally, why has the ASP.NET team decided to make the MapHttpRoute method so confusing. Why does it take two anonymous objects....how is anyone supposed to know what properties these objects actually need?
MSDN gives no help: http://msdn.microsoft.com/en-us/library/hh835483(v=vs.108).aspx
All the pain of a dynamically typed language without any of the benefit if you ask me...
Agree with you, it's a hell of a madness, you need to specify that the data parameter should be bound from the POST payload, since the Web API automatically assumes that it should be part of the query string (because it is a simple type):
public void SendData([FromBody] string data)
And to make the madness even worse you need to prepend the POST payload with = (yeah, that's not a typo, it's the equal sign):
curl -v -d "=test" http://localhost:8088/services/SendData
You could read more about the madness in this article.
Or stop the madness and try ServiceStack.
Use this signature and it will work every time.
public class TestController : ApiController
{
[HttpPost]
[ActionName("SendData")]
public HttpResponseMessage SendData(HttpRequestMessage request)
{
var data = request.Content.ReadAsStringAsync().Result;
Console.WriteLine(data);
}
}
Try with the following change,
public class TestController : ApiController
{
[HttpPost]
[ActionName("SendData")]
public void SendData(string data)
{
Console.WriteLine(data);
}
}
The ActionName attribute might fix the issue. Otherwise, you can also the name convention "Post"
public void Post(string data)
{
Console.WriteLine(data);
}
And send an Http Post directly to "services" without SendData.
ASP.NET MVC 2.0 will now, by default, throw an exception when an action attempts to return JSON in response to a GET request. I know this can be overridden on a method by method basis by using JsonRequestBehavior.AllowGet, but is it possible to set on a controller or higher basis (possibly the web.config)?
Update: Per Levi's comment, this is what I ended up using-
protected override JsonResult Json(object data, string contentType, System.Text.Encoding contentEncoding)
{
return Json(data, contentType, JsonRequestBehavior.AllowGet);
}
This, like other MVC-specific settings, is not settable via Web.config. But you have two options:
Override the Controller.Json(object, string, Encoding) overload to call Json(object, string, Encoding, JsonRequestBehavior), passing JsonRequestBehavior.AllowGet as the last argument. If you want this to apply to all controllers, then do this inside an abstract base controller class, then have all your controllers subclass that abstract class.
Make an extension method MyJson(this Controller, ...) which creates a JsonResult and sets the appropriate properties, then call it from your controller via this.MyJson(...).
There's another option. Use Action Filters.
Create a new ActionFilterAttribute, apply it to your controller or a specific action (depending on your needs). This should suffice:
public class JsonRequestBehaviorAttribute : ActionFilterAttribute
{
private JsonRequestBehavior Behavior { get; set; }
public JsonRequestBehaviorAttribute()
{
Behavior = JsonRequestBehavior.AllowGet;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var result = filterContext.Result as JsonResult;
if (result != null)
{
result.JsonRequestBehavior = Behavior;
}
}
}
Then apply it like this:
[JsonRequestBehavior]
public class Upload2Controller : Controller
MVC 2 block Json for GET requests for security reasons. If you want to override that behavior, check out the overload for Json that accepts a JsonRequestBehavior parameter.
public ActionResult Index()
{
return Json(data, JsonRequestBehavior.AllowGet)
}
I also got this error when I first use MVC 2.0 using my old code in MVC 1.0. I use fiddler to identify the cause of the error. See the steps on how to troubleshoot it using Fidder -
http://www.rodcerrada.com/post/2011/07/11/jQuery-getJSON()-does-not-tirgger-the-callback-in-ASPNET-MVC-2.aspx
Is this is the security issue MVC2 was trying to address?
http://haacked.com/archive/2009/06/25/json-hijacking.aspx
If so, it seems like the vulnerability is only an issue if you are trying to do a json call to an outside website. If your MVC2 app is only making json calls to your own website (to fill jqgrids for example), shouldn't you be able to safely override the Json call in your base controller to always allow get?
Just change JSON code from :
$.getJson("methodname/" + ID, null, function (data, textStatus)
to:
$.post("methodname/" + ID, null, function (data, textStatus)