I have a RESTful service in .NET and i would like all actions return an object of type JsonResult, JsonResult is an object defined by me like this:
public class JsonResult<T>
{
public Notify Notify {get; set;}
public T Data {get; set;}
public static CreateResponse(T Data, Notify Notify = null, HttpStatusCode Code = HttpStatusCode.OK)
{
//Code param not manage, at the moment
return new JsonResult<T>
{
Data = Data,
Notify = Notify
};
}
}
public class Notify
{
public string Message {get; set;}
public Severity Severity {get; set;}
}
public enum Severity
{
Error,
Info,
Warning,
Fatal,
}
so at the moment i have actions look like that:
public JsonResult<string> Get()
{
return JsonResult<string>.CreateResponse("Ciao Mondo!");
}
it works and i like this form 'cause when i read the firm i already know what i want to return to the client (JsonResult, T is the type of my data)... but the problem is when i want to manage the status code.
Before to create this new project, to manage the status code i used the HttpResponseMessage and the method Request.CreateResponse to return some data to the client:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "Ciao Mondo!");
}
but i don't like that 'cause it is not immediate to know the returned type.
so... my question is... how can i manage the status code into my JsonResult.CreateResponse(T, Notify[, HttpStatusCode]) method?
this method is moved into an CustomApiController (inherits from ApiController), so i have HttpContext and Request available.
Thanks so much to all
You could call Content which accepts a HttpStatusCode as well as a generic object. It does require you to change your method's return type to IHttpActionResult which is generally preferred.
public IHttpActionResult Get()
{
if(someErrorCondition)
return Content(HttpStatusCode.BadRequest, JsonResult<string>.CreateResponse("Ciao Mondo!"));
return Ok(JsonResult<string>.CreateResponse("Ciao Mondo!"));
}
Its preferred to use IHttpActionResult, don't forcefully define it to
JsonResult. This should be configurable through content-negotiation.
You can try as below:
public IHttpActionResult Get()
{
if(error)
BadRequest("Bad Request !!");
return Ok("Ciao Mondo!");
}
Your answer were be very helpful. I was looking for another solution, but you gave me a good idea and i have found this solution:
public class CustomApiController : ApiController
{
public class JsonResult<Target> : NegotiatedContentResult<Target>
{
public JsonResult(HttpStatusCode statusCode, Json<Target> content, ApiController controller) : base(statusCode, content.Data, controller)
{
this.Content = content;
}
public JsonResult(HttpStatusCode statusCode, Target content, ApiController controller) : base(statusCode, content, controller)
{
}
public JsonResult(HttpStatusCode statusCode, Target content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(statusCode, content, contentNegotiator, request, formatters)
{
}
public new Json<Target> Content { get; private set; }
}
public JsonResult<Target> CreateResponse<Target>(Target Data, string Notify, HttpStatusCode Code = HttpStatusCode.OK)
{
Json<Target> json = new Json<Target>
{
Notify = Notify,
Data = Data
};
return new JsonResult<Target>(Code, json, this);
}
}
so i can inherit from CustomApiController and write action like that:
public JsonResult<IEnumerable<string>> Get(bool test)
{
if (test)
{
return this.CreateResponse(new string[] { "test1", "test2", "test3" } as IEnumerable<string>, null, System.Net.HttpStatusCode.OK);
}
else
{
return this.CreateResponse(new string[] { "test1", "test2", "test3" } as IEnumerable<string>, null, System.Net.HttpStatusCode.BadRequest);
}
}
thanks a lot!
Related
I have base class for every request in my app:
public abstract class BaseDto
{
public string Uid { get; set; }
}
public class RequestDto : BaseDto
{
public string SomeData { get; set; }
}
Im using my ReuqestDto class in my controller actions:
[HttpGet]
public IEnumerable<string> Get(RequestDto req)
{
// some logic on request
if (req.Uid != null)
{
// perform action
}
}
The user passing only SomeData property to me. In my JWT Token i have saved some information about Uid for BaseDto. What is the best way to write data to Uid using middleware/filter to have that information in my Get() method? I Tried to serialized HttpContext.Request.Body but not success because i cant find, how to do it properly. Or maybe there are better solutions for this problem? How to write data to my incoming objects in app?
This is probably what you want.
You should to create own interface for models like that
public interface IMyRequestType { }
Your model should implement it for finding model in FilterAttribute
public class MyModel : IMyRequestType
{
public string ID { get; set; }
}
And create your filter attribute with OnActionExecuting implentation
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof(MyFilterImpl)) { }
private class MyFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public MyFilterAttributeImpl(ILoggerFactory loggerFactory)
{
// get something from DI
_logger = loggerFactory.CreateLogger<MyFilterAttributeImpl>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// get your request model
var model = context.ActionArguments.Values.OfType<IMyRequestType>().Single();
// get your key
//context.HttpContext.User or whatever
// do something with model
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some logic work
}
}
}
I often created a filter which implements Attribute and IAsyncActionFilter to get the information before go inside the Controller's action.
Here is an example,
using System.IdentityModel.Tokens.Jwt;
public class UserProfileFilter : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string uid = string.Empty;
StringValues authHeaderVal = default(StringValues);
// Get UID from JWT
if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out authHeaderVal))
{
string bearerTokenPrefix = "Bearer";
string accessToken = string.Empty;
string authHeaderStr = authHeaderVal.ToString();
if (!string.IsNullOrEmpty(authHeaderStr) && authHeaderStr.StartsWith(bearerTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
accessToken = authHeaderStr.Replace(bearerTokenPrefix, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
}
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(accessToken);
uid = token.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Or Get UID from ActionExecutingContext
var user = context.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
uid = user.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Get payload
RequestDto payload = (RequestDto)context.ActionArguments?.Values.FirstOrDefault(v => v is RequestDto);
payload.Uid = uid;
await next();
}
}
And then you can put the filter on any action.
[HttpPost]
[Authorize]
[TypeFilter(typeof(UserProfileFilter))]
public ActionResult<IActionResult> AdminGet(RequestDto request)
{
Debug.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(request));
return this.Ok();
}
The above filter will use the sub claim's value to overwrite the value of the incoming payload.
For example, if I post the payload as following,
{
"uid" : "",
"someData": "Test"
}
The action will finally output {"Uid":"MyID","SomeData":"Test"}.
I have a controller that has multiple routes.
I am trying to call an endpoint stated as
GET: api/lookupent/2020-03-17T13:28:37.627691
but this results in this error
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Controllers.RecordController.Get (API)
Controllers.RecordController.GetRecordRegisteredAt (API)
but I am not sure I understand why this makes sense since this code
// GET: api/{RecordName}/{id}
[HttpGet("{RecordName}/{id}", Name = "GetRecord")]
public ActionResult Get(string RecordName, long id)
// GET: api/{RecordName}/{timestamp}
[HttpGet("{RecordName}/{timestamp}", Name = "GetRecordRegisteredAt")]
public ActionResult GetRecordRegisteredAt(string RecordName, string timestamp)
why does the input match with these endpoints?
You can fix this using route constraints.
Take a look at https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
Here's their example:
[Route("users/{id:int}")]
public User GetUserById(int id) { ... }
[Route("users/{name}")]
public User GetUserByName(string name) { ... }
The problem you have is that your controller has the same routing for 2 different methods receiving different parameters.
Let me illustrate it with a similar example, you can have the 2 methods like this:
Get(string entityName, long id)
Get(string entityname, string timestamp)
So far this is valid, at least C# is not giving you an error because it is an overload of parameters. But with the controller, you have a problem, when aspnet receives the extra parameter it doesn't know where to redirect your request.
You can change the routing which is one solution.
This solution gives you the ability to map your input to a complex type as well, otherwise use Route constraint for simple types
Normally I prefer to keep the same names and wrap the parameters on a DtoClass, IntDto and StringDto for example
public class IntDto
{
public int i { get; set; }
}
public class StringDto
{
public string i { get; set; }
}
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
but still, you have the error. In order to bind your input to the specific type on your methods, I create a ModelBinder, for this scenario, it is below(see that I am trying to parse the parameter from the query string but I am using a discriminator header which is used normally for content negotiation between the client and the server(Content negotiation):
public class MyModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
dynamic model = null;
string contentType = bindingContext.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == HeaderNames.Accept).Value;
var val = bindingContext.HttpContext.Request.QueryString.Value.Trim('?').Split('=')[1];
if (contentType == "application/myContentType.json")
{
model = new StringDto{i = val};
}
else model = new IntDto{ i = int.Parse(val)};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Then you need to create a ModelBinderProvider (see that if I am receiving trying to bind one of these types, then I use MyModelBinder)
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(IntDto) || context.Metadata.ModelType == typeof(StringDto))
return new MyModelBinder();
return null;
}
and register it into the container
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});
}
So far you didn't resolve the issue you have but we are close. In order to hit the controller actions now, you need to pass a header type on the request: application/json or application/myContentType.json. But in order to support conditional logic to determine whether or not an associated action method is valid or not to be selected for a given request, you can create your own ActionConstraint. Basically the idea here is to decorate your ActionMethod with this attribute to restrict the user to hit that action if he doesn't pass the correct media type. See below the code and how to use it
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class RequestHeaderMatchesMediaTypeAttribute : Attribute, IActionConstraint
{
private readonly string[] _mediaTypes;
private readonly string _requestHeaderToMatch;
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
}
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes, int order)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
Order = order;
}
public int Order { get; set; }
public bool Accept(ActionConstraintContext context)
{
var requestHeaders = context.RouteContext.HttpContext.Request.Headers;
if (!requestHeaders.ContainsKey(_requestHeaderToMatch))
{
return false;
}
// if one of the media types matches, return true
foreach (var mediaType in _mediaTypes)
{
var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(),
mediaType, StringComparison.OrdinalIgnoreCase);
if (mediaTypeMatches)
{
return true;
}
}
return false;
}
}
Here is your final change:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/json" })]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/myContentType.json" })]
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
Now the error is gone if you run your app. But how you pass the parameters?:
This one is going to hit this method:
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
And this one the other one:
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
Run it and let me know
I had the same issue for these two methods:
[HttpPost]
public async Task<IActionResult> PostFoos(IEnumerable<FooModelPostDTO> requests)
[HttpPost]
public async Task<IActionResult> GetFoos(GetRequestDTO request)
The first one is for getting entities (using Post) and the second one is for posting new entities in DB (again using Post).
One possible solution is to distinguish between them by their's method names (../[action]) with the Route attribute:
[Route("api/[controller]/[action]")]
[ApiController]
public class FoosController : ControllerBase
In my WebApi I have a HttpGet and HttpPost method, the get method is working fine and the post method is called but the body content is always null, unless used in a HttpRequestMessage. I tried providing the body content in a string format(preferred datatype) aswell as in a model but neither one of those methods worked. I also tried switching the content type without success. Does anyone know if I'm doing something wrong or how I can easily get the variable data from the HttpRequestMessage, which in the example below is "test".
Method 1:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]string filecontent, string companycode)
{
MessageBox.Show(filecontent);
Return Ok("");
}
Method 2 (with model):
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]ItemXML filecontent, string companycode)
{
MessageBox.Show(filecontent.XMLContent);
Return Ok("");
}
Model:
public class ItemXML
{
public ItemXML(string content)
{
XMLContent = content;
}
public string XMLContent { get; set; }
}
Method 3:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem(HttpRequestMessage filecontent, string companycode)
{
var content = filecontent.Content.ReadAsStringAsync().Result;
MessageBox.Show(content);
Return Ok("");
}
Method 3 content string ("test" is the provided value): " content "------WebKitFormBoundarydu7BJizb50runvq0\r\nContent-Disposition: form-data; name=\"filecontent\"\r\n\r\n\"test\"\r\n------WebKitFormBoundarydu7BJizb50runvq0--\r\n" string"
Create a model store data to be sent to server
public class Model {
public string filecontent { get; set;}
public string companycode { get; set;}
}
Update Action
[HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]Model model) {
if(ModelStat.IsValid) {
return Ok(model); //...just for testing
}
return BadRequest();
}
On the client make sure the request is being sent properly. In this case going to use JSON.
public client = new HttpClient();
var model = new {
filecontent = "Hello World",
companycode = "test"
};
var response = await client.PostAsJsonAsync(url, model);
If using another type of client ensure that the data being sent is formatted correctly for the Web API action to accept the request.
Reference Parameter Binding in ASP.NET Web API
Can a System.Web.Http endpoint be an IObservable? Can it be done while still using Attributes such as Route and AcceptVerbs?
Basically, I want to make the following code example into an IObservable stream so I'm subscribing and handling it via reactive extensions:
[Route("{id}")]
[AcceptVerbs("GET")]
public async Task<IHttpActionResult> example(int id)
{
return Ok(await Service.GetExampleAsync(id));
}
Edit:
I'm looking for a more elegant solution like https://blogs.msdn.microsoft.com/jeffva/2010/09/15/rx-on-the-server-part-5-of-n-observable-asp-net (Observable from IHttpHandler) but hopefully more mature after seven years?
There is no direct way. But you could publish your own Observable. The main problem is, you need to return a value in the example function.
One solution could be to create an Observable in which you pass a TaskCompletionSource. This would allow you to set the result from the Event handler.
public class Request
{
public int Parameter { get; }
public Request(int parameter)
{
Parameter = parameter;
}
public TaskCompletionSource<IHttpActionResult> Result { get; } = new TaskCompletionSource<IHttpActionResult>();
}
public class Handler
{
public Subject<Request> ExampleObservable { get; } = new Subject<Request>();
[Route("{id}")]
[AcceptVerbs("GET")]
public async Task<IHttpActionResult> example(int id)
{
var req = new Request(id);
ExampleObservable.OnNext(req);
return await req.Result.Task;
}
}
In the example above, we push a Request in the ExampleObservable. You can subscribe to this and use Request.Result.SetResult(...) to return the request.
ExampleObservable.Subscribe(req => req.Result.SetResult(Ok(Service.GetExample(id)));
I'm looking at the documentation of WebAPI 2, and i'm severely disappointed with the way the action results are architected. I really hope there is a better way.
So documentation says I can return these:
**void** Return empty 204 (No Content)
**HttpResponseMessage** Convert directly to an HTTP response message.
**IHttpActionResult** Call ExecuteAsync to create an HttpResponseMessage, then convert to an HTTP response message.
**Other type** Write the serialized return value into the response body; return 200 (OK).
I don't see a clean way to return an array of items with custom HTTP status code, custom headers and with auto negotiated content though.
What I would like to see is something like
public HttpResult<Item> Post()
{
var item = new Item();
var result = new HttpResult<Item>(item, HttpStatusCode.Created);
result.Headers.Add("header", "header value");
return result;
}
This way I can glance over a method and immediately see whats being returned, and modify status code and headers.
The closest thing I found is NegotiatedContentResult<T>, with weird signature (why does it need an instance of controller?), but there's no way to set custom headers?
Is there a better way ?
The following code should give you everything you want:
[ResponseType(typeof(Item))]
public IHttpActionResult Post()
{
var item = new Item();
HttpContext.Current.Response.AddHeader("Header-Name", "Header Value");
return Content(HttpStatusCode.Created, item);
}
... if you really need to return an array of items ...
[ResponseType(typeof(List<Item>))]
public IHttpActionResult Post()
{
var items = new List<Item>();
// Do something to fill items here...
HttpContext.Current.Response.AddHeader("Item-Count", items.Count.ToString());
return Content(HttpStatusCode.Created, items);
}
I don't think the designers of the web-api intended for controller methods to be fiddling with the headers.
The design pattern seems to be to use DelegatingHandler, ActionFilterAttribute and the ExecuteAsync overridable method of ApiController to handle authentication and response formatting.
So perhaps your logic for message content negotiation should be handled there ?
However if you definitely need to control headers from within your controller method you can do a little set-up to make it work.
To do so you can create your own DelegationHandler that forwards selected headers from your "Inner" response headers:
public class MessageHandlerBranding : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//If we want to forward headers from inner content we can do this:
if (response.Content != null && response.Content.Headers.Any())
{
foreach (var hdr in response.Content.Headers)
{
var keyUpr = hdr.Key.ToUpper(); //Response will not tolerate setting of some header values
if ( keyUpr != "CONTENT-TYPE" && keyUpr != "CONTENT-LENGTH")
{
string val = hdr.Value.Any() ? hdr.Value.FirstOrDefault() : "";
response.Headers.Add(hdr.Key, val);
}
}
}
//Add our branding header to each response
response.Headers.Add("X-Powered-By", "My product");
return response;
}
}
Then you register this handler in your web-api configuration, this is usually in the GlobalConfig.cs file.
config.MessageHandlers.Add(new MessageHandlerBranding());
You could also write your own custom class for the response object like this:
public class ApiQueryResult<T> : IHttpActionResult where T : class
{
public ApiQueryResult(HttpRequestMessage request)
{
this.StatusCode = HttpStatusCode.OK; ;
this.HeadersToAdd = new List<MyStringPair>();
this.Request = request;
}
public HttpStatusCode StatusCode { get; set; }
private List<MyStringPair> HeadersToAdd { get; set; }
public T Content { get; set; }
private HttpRequestMessage Request { get; set; }
public void AddHeaders(string headerKey, string headerValue)
{
this.HeadersToAdd.Add(new MyStringPair(headerKey, headerValue));
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = this.Request.CreateResponse<T>(this.StatusCode, this.Content);
foreach (var hdr in this.HeadersToAdd)
{
response.Content.Headers.Add(hdr.key, hdr.value);
}
return Task.FromResult(response);
}
private class MyStringPair
{
public MyStringPair(string key, string value)
{
this.key = key;
this.value = value;
}
public string key;
public string value;
}
}
And use it like this in your controller:
[HttpGet]
public ApiQueryResult<CustomersView> CustomersViewsRow(int id)
{
var ret = new ApiQueryResult<CustomersView>(this.Request);
ret.Content = this.BLL.GetOneCustomer(id);
ret.AddHeaders("myCustomHkey","myCustomValue");
return ret;
}