UrlHelper.Link() returns null - asp.net-web-api2

I know that there are a number of questions here that have this title, but, having been through them, I do not believe any of the posted solutions help.
I have the following code in a Web Api v2.1 Controller...
[VersionedRoute("{id:int}", 1, Name="GetEvent")]
[HttpGet]
public IHttpActionResult Get(int id)
{
try
{
var helper = new UrlHelper(Request);
var link = helper.Link("GetEvent", new { id = id });
var result = ApiBL.V1Get(id);
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (Exception ex)
{
throw new ApiHttpResponseException(HttpStatusCode.BadRequest, ex);
}
}
Putting a breakpoint on the line after the line on which link is assigned, I can see that it is null. I can also see that Request has what I'd expect to see...
{Method: GET, RequestUri: 'http://localhost:8087/atlas/api/events/50', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Authorization: Basic blahblahblah
Host: localhost:8087
Content-Length: 31
Content-Type: application/json
}}
And that helper is non-null.
Below is the definition of the VersionedRouteAttribute class
public class VersionedRouteAttribute : RouteFactoryAttribute
{
public VersionedRouteAttribute(string template, int allowedVersion)
: base(template)
{
AllowedVersion = allowedVersion;
}
public int AllowedVersion { get; private set; }
public override IDictionary<string, object> Constraints
{
get
{
var constraints = new HttpRouteValueDictionary();
constraints.Add("version", new VersionConstraint(AllowedVersion));
return constraints;
}
}
}
I believe, from everything I've read, that this should work. I simply can't figure out what's wrong. I'd appreciate any pointers.

I believe you are using the RoutingConstraintsSample from here which demonstrates versioning with attribute routing...if yes, then the VersionConstraint in that sample has a bug...you can modify the Match method of that to like below:
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection == HttpRouteDirection.UriResolution)
{
int version = GetVersionHeader(request) ?? DefaultVersion;
return (version == AllowedVersion);
}
return true;
}
Also why are you instantiating UrlHelper yourself as its already provided to you by the Url property that is available on the controller.

Related

.NET CORE WEB API accept list of integers as an input param in HTTP GET API

I am using .net core 3+ web api.
Below is how my action looks like below, it uses HTTP GET and I want to pass few fields and one of the fields is a list of integers.
[HttpGet]
[Route("cities")]
public ActionResult<IEnumerable<City>> GetCities([FromQuery] CityQuery query)
{...}
and here is CityQuery class -
public class CityQuery
{
[FromQuery(Name = "stateids")]
[Required(ErrorMessage = "stateid is missing")]
public string StateIdsStr { get; set; }
public IEnumerable<int> StateList
{
get
{
if (!string.IsNullOrEmpty(StateIdsStr))
{
var output = StateIdsStr.Split(',').Select(id =>
{
int.TryParse(id, out var stateId);
return stateId;
}).ToList();
return output;
}
return new List<int>();
}
}
}
Is there a generic way I can use to accept list of integers as input and not accept string and then parse it?
Or is there a better way to do this? I tried googling but could not find much. Thanks in advance.
This can help
[HttpGet]
[Route("cities")]
public ActionResult<IEnumerable<City>> GetCities([FromQuery] int[] stateids)
{
...
}
but the query string will change to
https://localhost/api/controller/cities?stateids=1&stateids=2&stateids=3
If you required comma separated query string with integer, you can go for Custom model binder
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1
You can use custom model binding, below is a working demo:
Model:
public class CityQuery
{
public List<int> StateList{ get; set; }
}
CustomModelBinder:
public class CustomModelBinder: IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var values = bindingContext.ValueProvider.GetValue("stateids");
if (values.Length == 0)
{
return Task.CompletedTask;
}
var splitData = values.FirstValue.Split(',');
var result = new CityQuery()
{
StateList = new List<int>()
};
foreach(var id in splitData)
{
result.StateList.Add(int.Parse(id));
}
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
Applying ModelBinding Attribute on Action method:
[HttpGet]
[Route("cities")]
public ActionResult GetCities([ModelBinder(BinderType = typeof(CustomModelBinder))] CityQuery query)
{
return View();
}
when the url like /cities?stateids=1,2,3, the stateids will be filled to StateList
I think you just need to use [FromUri] before int array parameter :
public ActionResult<IEnumerable<City>> GetCities([FromUri] int[] stateList)
And request would be like :
/cities?stateList=1&stateList=2&stateList=3

Is there a way to handle asp.net core odata errors

Is there a way to handle asp.net core odata errors?
I have a model class DimDateAvailable with one property, a primary key of int DateId, and I make a call like /data/DimDateAvailable?$select=test.
Other calls work as expected and return what I'm after - this is a deliberate call to generate a fault, and it fails because there is no property named test on the model. The response comes back as expected, like so: {"error":{"code":"","message":"The query specified in the URI is not valid. Could not find a property named 'test' on type 'DimDateAvailable'... followed by a stack trace.
This response is fine when env.IsDevelopment() is true but I don't want to expose the stack trace when not in development.
I've looked at wrapping the code in the controllers' get method in a try-catch, but I think there's an action filter running over the results so it never gets called. On the other hand, I can't see where to inject any middleware and/or add any filters to catch errors. I suspect there might be a way to override an output formatter to achieve what I want but I can't see how.
Here's what I have at the moment:
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TelemetryDbContext>();
services.AddOData();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "data", GetEdmModel());
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// insert special bits for e.g. custom MLE here
routeBuilder.EnableDependencyInjection();
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<DimDateAvailable>("DimDateAvailable");
return builder.GetEdmModel();
}
In TelemetryDbContext.cs:
public virtual DbSet<DimDateAvailable> DimDateAvailable { get; set; }
In DimDateAvailable.cs
public class DimDateAvailable
{
[Key]
public int DateId { get; set; }
}
My controller:
public class DimDateAvailableController : ODataController
{
private readonly TelemetryDbContext data;
public DimDateAvailableController(TelemetryDbContext data)
{
this.data = data;
}
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IActionResult Get()
{
return Ok(this.data.DimDateAvailable.AsQueryable());
}
}
This is in an asp.net core 2 web app with the Microsoft.AspNetCoreOData v7.0.1 and EntityFramework 6.2.0 packages.
Investigating Ihar's suggestion lead me down the rabbit hole, and I ended up inserting an ODataOutputFormatter into the MVC options to intercept ODataPayloadKind.Error responses and reformat them.
It was interesting to see that context.Features held an instance of IExceptionHandlerFeature in app.UseExceptionHandler() but not in the ODataOutputFormatter. That lack was pretty much what prompted me to pose this question in the first place, but was solved by translating the context.Object in the ODataOutputFormatter which is something I saw done in the OData source as well. I don't know if the changes below are good practice in asp.net core or when using the AspNetCoreOData package, but they do what I want for now.
Changes to Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TelemetryDbContext>();
services.AddOData();
services.AddMvc(options =>
{
options.OutputFormatters.Insert(0, new CustomODataOutputFormatter(this.Environment.IsDevelopment()));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Added this to catch errors in my own code and return them to the client as ODataErrors
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
if (error?.Error != null)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var response = error.Error.CreateODataError(!env.IsDevelopment());
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
// when no error, do next.
else await next();
});
});
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "data", GetEdmModel());
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// insert special bits for e.g. custom MLE here
routeBuilder.EnableDependencyInjection();
});
}
New classes CustomODataOutputFormatter.cs and CommonExtensions.cs
public class CustomODataOutputFormatter : ODataOutputFormatter
{
private readonly JsonSerializer serializer;
private readonly bool isDevelopment;
public CustomODataOutputFormatter(bool isDevelopment)
: base(new[] { ODataPayloadKind.Error })
{
this.serializer = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() };
this.isDevelopment = isDevelopment;
this.SupportedMediaTypes.Add("application/json");
this.SupportedEncodings.Add(new UTF8Encoding());
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
if (!(context.Object is SerializableError serializableError))
{
return base.WriteResponseBodyAsync(context, selectedEncoding);
}
var error = serializableError.CreateODataError(this.isDevelopment);
using (var writer = new StreamWriter(context.HttpContext.Response.Body))
{
this.serializer.Serialize(writer, error);
return writer.FlushAsync();
}
}
}
public static class CommonExtensions
{
public const string DefaultODataErrorMessage = "A server error occurred.";
public static ODataError CreateODataError(this SerializableError serializableError, bool isDevelopment)
{
// ReSharper disable once InvokeAsExtensionMethod
var convertedError = SerializableErrorExtensions.CreateODataError(serializableError);
var error = new ODataError();
if (isDevelopment)
{
error = convertedError;
}
else
{
// Sanitise the exposed data when in release mode.
// We do not want to give the public access to stack traces, etc!
error.Message = DefaultODataErrorMessage;
error.Details = new[] { new ODataErrorDetail { Message = convertedError.Message } };
}
return error;
}
public static ODataError CreateODataError(this Exception ex, bool isDevelopment)
{
var error = new ODataError();
if (isDevelopment)
{
error.Message = ex.Message;
error.InnerError = new ODataInnerError(ex);
}
else
{
error.Message = DefaultODataErrorMessage;
error.Details = new[] { new ODataErrorDetail { Message = ex.Message } };
}
return error;
}
}
Changes to the controller:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IQueryable<DimDateAvailable> Get()
{
return this.data.DimDateAvailable.AsQueryable();
}
If you want a customization of responses, including customization of error responses try to use ODataQueryOptions instead of using
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
Check some samples at https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly
It would allow you to cache validation errors and build custom response.
I have had this issue in the past and the only one way I got this working without having to write a middleware was like:
Try this:
catch (ODataException ex)
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;//This line is important, if not it will return 500 Internal Server Error.
return BadRequest(ex.Message);//Just respond back the actual error which is 100% correct.
}
Then the error will look like:
{
"#odata.context": "http://yourendpoint.com$metadata#Edm.String",
"value": "The property 'test' cannot be used in the $select query option."
}
Hope this helps.
Thanks

Restore AJAX handling for ASP.NET Core to previous functionality

In previous MVC5 and below, you could make an ajax call that unwrapped the parameters properly:
JS:
$.post('/controller/endpoint',{intparam: 1, strparam: 'hello'})
CS:
public ActionResult endpoint(int intparam, string strparam){}
In the new aspnetcore, it has changed:
CS:
public CustomClassWrapper{
public int intparam {get;set;}
public string stringparam {get;set;}
}
public ActionResult endpoint([FromBody]CustomClassWrapper item){}
To sum it up, in the new framework, you need to write a wrapper class and can only pass one [FromBody] parameter to the method. Previously, the params would be unwrapped by variable name correctly.
So, i'm trying to re-implement this functionality in an aspnetcore middleware component. I'm having difficulty in how to accomplish calling the controller method properly with the parameters.
My current cut-down code:
public async Task Invoke(HttpContext context)
{
if (IsAjaxRequest(context.Request))
{
try
{
string bodyContent = new StreamReader(context.Request.Body).ReadToEnd();
var parameters = JsonConvert.DeserializeObject(bodyContent);
///What to do here?
}
catch (Exception ex)
{
throw new Exception("AJAX method not found ", ex);
}
}
else
{
await _next(context);
}
}
I'm really just not sure about what to do after deserializing the parameters. I have the URL for the endpoint and also the params correctly. Just need to know how to call the method and return the result as JSON. Should i be using Reflection to get the controller method? Or is there a better way using MVC?
Try implement custom IModelBinder.
public class BodyFieldModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
bindingContext.HttpContext.Request.EnableRewind(); // required to read request body multiple times
var inputStream = bindingContext.HttpContext.Request.Body;
if (inputStream.Position != 0L)
inputStream.Position = 0;
var bodyValue = new StreamReader(inputStream, Encoding.UTF8).ReadToEnd();
var jsonObject = (JObject)JsonConvert.DeserializeObject<object>(bodyValue);
if (jsonObject.TryGetValue(bindingContext.FieldName, out var jToken))
{
var jsonSerializer = JsonSerializer.Create();
var result = jToken.ToObject(bindingContext.ModelType, jsonSerializer);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}
Be careful, the code above lacks error handling and etc.
And use it like this:
[HttpPost]
public IActionResult Endpoint([ModelBinder(typeof(BodyFieldModelBinder))] int intparam)
Also you could implement custom attribute to reduce complexity of declaration:
public class BodyFieldAttribute : ModelBinderAttribute
{
public BodyFieldAttribute()
: base(typeof(BodyFieldModelBinder))
{
}
}
it's very simple thing i don't know why it not working at your end
JS
$.post('actionMethodURl', { FirstName: '1', LastName: 'hello' }).done(Successfunction);
CS
[HttpPost]
public ActionResult endpoint(string FirstName,string LastName)
{
object Message = string.Empty;
if (ModelState.IsValid)
{
Message = "Pass";
}
else
{
Message = ModelState.Errors();
}
return Json(Message);
}

WebApi Model Binding For Inherited Types

I'm looking to handle model binding for an inherited type in WebApi, and what I'm really looking to do is to handle the binding using the default model binding (other than selecting the type where it's unable to do so), but I'm missing something fundamental.
So say I have the types:
public abstract class ModuleVM
{
public abstract ModuleType ModuleType { get; }
}
public class ConcreteVM : ModuleVM
{
}
Using an MVC controller, I would do something like this:
public class ModuleMvcBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(ModuleVM))
{
// Just hardcoding the type for simplicity
Type instantiationType = typeof(ConcreteVM);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ModuleMvcBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new ModuleMvcBinder();
}
}
Then use the attribute on the controller and all is well, and I'm leveraging the DefaultModelBinder for the real work and I'm essentially just providing the correct object instantiation.
So how do I do the same for the WebApi version?
If I use a custom model binder (e.g. Error implementing a Custom Model Binder in Asp.Net Web API), my problem is (I believe) that in the BindModel method I haven't found a good way to use the "standard" http binding once I instantiate the object. I can do it specifically for JSON (Deserialising Json to derived types in Asp.Net Web API) or XML (Getting my Custom Model bound to my POST controller) as suggested in other posts, but it seems to me that's defeating the point since web api should be seperating that, and is - it just doesn't know how to determine the type. (All concrete types naturally are handled just fine.)
Am I overlooking something obvious I should be directing the BindModel call to after instantiating the object?
Following is an example where I have inheritance in my types and after some settings (like decorating with KnownType attributes, required by Xml formatter's datacontractserializer) and TypeNameHandling setting on Json formatter, we can expect consistent behavior across both xml/json requests.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}

MVC Web Api returning serialized response instead of css

I am having an issue returning css from a web api controller. The code takes a request for a css file and returns it after reading it from the database.
The problem is that the web api code seems to be serializing the response and returning that instead of the css itself.
Here you can see a link tag that the browser is sending to the server which should return css. You can also see that the response looks like a serialization of my css instead of just the css string.
My request and response headers:
My controller looks like this:
public HttpResponseMessage Get(string fileName, string siteId, int id)
{
var fileData = ReadSomeCssFromTheDatabase();
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(fileData);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
result.Headers.CacheControl = new CacheControlHeaderValue();
result.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
result.Headers.CacheControl.MustRevalidate = true;
return result;
}
There is a “text/css” formatter installed that is being created but not being hit for some reason.
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskCompletionSource = new TaskCompletionSource<object>();
try
{
var memoryStream = new MemoryStream();
readStream.CopyTo(memoryStream);
var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
taskCompletionSource.SetResult(s);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override bool CanWriteType(Type type)
{
return false;
}
}
What am I doing wrong?
Your formatter would not be hit because you are not going through content negotiation process (as you are returning HttpResponseMessage in your action...you could use Request.CreateResponse<> to make conneg process run)
You are trying to 'write' the css content right?...but i see that CanWriteType is returning 'false' and also you seem to be overriding ReadFromStreamAsync instead of WriteToStreamAsync?
An example of how you could do(from what i understood about the above scenario):
public class DownloadFileInfo
{
public string FileName { get; set; }
public string SiteId { get; set; }
public int Id { get; set; }
}
public HttpResponseMessage Get([FromUri]DownloadFileInfo info)
{
// validate the input
//Request.CreateResponse<> would run content negotiation and get the appropriate formatter
//if you are asking for text/css in Accept header OR if your uri ends with .css extension, you should see your css formatter getting picked up.
HttpResponseMessage response = Request.CreateResponse<DownloadFileInfo>(HttpStatusCode.OK, info);
response.Headers.CacheControl = new CacheControlHeaderValue();
response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
response.Headers.CacheControl.MustRevalidate = true;
return response;
}
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == typeof(DownloadFileInfo);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
//use the 'value' having DownloadFileInfo object to get the details from the database.
// Fead from database and if you can get it as a Stream, then you just need to copy it to the 'writeStream'
}
}