I want to upload an Image for my webapi project and i am using WebImage class in Asp.net MVC 4 for Saving, cropping, rotating image by using this class.
I include WebHelper in ApiController with same functionality like mvc project
My problem is in webapi project is when i upload image in Webapi controller i receive an error :
{
Message: "An error has occurred."
ExceptionMessage: "No MediaTypeFormatter is available to read an object of type 'WebImage' from content with media type 'multipart/form-data'."
ExceptionType: "System.InvalidOperationException"
StackTrace: " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) at System.Web.Http.Controllers.HttpActionBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(HttpParameterBinding parameterBinder) at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() at System.Threading.Tasks.TaskHelpers.IterateImpl(IEnumerator`1 enumerator, CancellationToken cancellationToken)"
}
and my upload method sample:
[HttpPost]
public HttpResponseMessage filer(WebImage data)
{
HttpResponseMessage response = null;
if (data == null)
{
response = new HttpResponseMessage()
{
Content = new StringContent("Not a image file"),
StatusCode = HttpStatusCode.BadRequest
};
}
else {
response = new HttpResponseMessage()
{
Content = new StringContent(data.FileName.ToString()),
StatusCode = HttpStatusCode.OK
};
}
return response;
}
Please Explain me how to add MediaTypeFormatter to support WebImage class.
There are two approaches that involve not using a MediaFormatter, these would involve creating a custom ModelBinder or implementing an model class that accepts a base64 encoded string or a byte array to accept the data, then converting the data from that model class into a WebImage. However to answer the question, the process is very straightforward. Here is one implementation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Formatting;
using System.Web;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Helpers;
using System.Net.Http.Headers;
using System.IO;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Diagnostics;
namespace StackOverFlowWI.Infrastructure
{
public class WebImageMediaFormatter : MediaTypeFormatter
{
public WebImageMediaFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
public override bool CanReadType(Type type)
{
return type == typeof(WebImage);
}
public override bool CanWriteType(Type type)
{
return false;
}
public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
byte[] buffer = new byte[content.Headers.ContentLength.Value];
while (await readStream.ReadAsync(buffer, (int)readStream.Position, buffer.Length - (int) readStream.Position) > 0) { }
string stringData = Encoding.Default.GetString(buffer);
JObject myJson = JObject.Parse(stringData);
JToken myJToken = myJson.GetValue("imageBytes");
byte[] myBytes = myJToken.Values().Select(x => (byte)x).ToArray();
return new WebImage(myBytes);
}
}
}
You have to register the mediaformatter in the instance of the HttpConfiguration object formatters collection in an IIS hosted application this would be in the WebApiConfig.Register method.
config.Formatters.Insert(0, new WebImageMediaFormatter());
I thought this was an interesting question so went through an implementation and am including some javascript code for the sake of completeness:
var ajaxCall = function (data) {
dataString = data.toString();
dataString = "[" + dataString + "]";
dataString = JSON.parse(dataString);
console.log(dataString.length);
//console.log(dataString);
var imageData = {};
imageData.imageBytes = dataString;
console.log(imageData);
//console.log(imageData);
var ajaxOptions = {};
ajaxOptions.url = "/api/image/PostWebImage";
ajaxOptions.type = "Post";
ajaxOptions.contentType = "application/json";
ajaxOptions.data = JSON.stringify(imageData);
ajaxOptions.success = function () {
console.log('no error detected');
};
ajaxOptions.error = function (jqXHR) {
console.log(jqXHR);
};
$.ajax(ajaxOptions);
};
var postImage = function () {
var file = $('input[type=file]')[0].files[0];
var myfilereader = new FileReader();
myfilereader.onloadend = function () {
var uInt8Array = new Uint8Array(myfilereader.result);
ajaxCall(uInt8Array);
}
if (file) {
myfilereader.readAsArrayBuffer(file);
} else {
console.log("failed to read file");
}
};
Also keep in mind the hard coded limit in that web api accepts a limited amount of data unless you modify the web.config file to modify the httpRuntime environment to accept large requests. (This is assuming that you do not buffer the upload into chunks which would be a better approach).
<httpRuntime targetFramework="4.5" maxRequestLength="1024000" />
Finally an alternate solution that would not require a mediaformatter as mentioned above would be to create a model class with a public property that accepts the data as it is sent.
namespace StackOverFlowWI.Models
{
public class myModel
{
public byte [] imageBytes { get; set; }
}
}
You could then create the object in your action method.
public IHttpActionResult Post( myModel imageData )
{
WebImage myWI = new WebImage(imageData.imageBytes);
string path = System.Web.Hosting.HostingEnvironment.MapPath("~/Images/somefile.png");
myWI.Save(path);
return Ok();
}
For future reference keep in mind that default model binder implementations in web api do not accept any class as a parameter in an action method that does not have a parameter-less constructor. The only exception to this rule is when dependency injection with dependency injection add ins such as ninject or unity are used.
Related
I am trying to make a web API Post method call as follows but it not working as expected,xmlcontent seems OK but somehow the formatting seems messed up when the request is being sent and the response throws an error ,I double checked the XML from python and it works,is there a better way to create and send the XML?what am I doing wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
namespace WebApiXML
{
public class Program
{
static void Main(string[] args)
{
testWCF2(); //Or whatever
Console.ReadLine();
}
public static async Task testWCF2()
{
string xmlcontent = #"<SoftwareProductBuild>
<BuildSource>DATASOURCE</BuildSource>
<BuiltBy>username1</BuiltBy>
<CreatedBy>username1</CreatedBy>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Status>Approved</Status>
<BuiltOn>2017-06-27T06:20:30.275690</BuiltOn>
<Tag>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_test2</Tag>
<Keywords>
<KeywordInfo>
<Name>subystem</Name>
</KeywordInfo>
</Keywords>
<SoftwareImageBuilds>
<SoftwareImageBuild>
<Type>LA</Type>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Location>\\location1\data1\PRECOMMIT_OS_DEF</Location>
<Variant>PRECOMMIT_OS_DEF</Variant>
<LoadType>Direct</LoadType>
<Target>msm8998</Target>
<SoftwareImages>
<SoftwareImage>
<Name>UL.AB.1.2</Name>
</SoftwareImage>
</SoftwareImages>
</SoftwareImageBuild>
</SoftwareImageBuilds>
</SoftwareProductBuild>";
#region using
using (var client = new System.Net.Http.HttpClient())
{
var response = await client.PostAsXmlAsync("http://server:8100/api/SoftwareProductBuild", xmlcontent);
if (!response.IsSuccessStatusCode)
{
//throw new InvalidUriException("Some error with details.");
Console.WriteLine(response);
}
Console.WriteLine("Printing DEV Pool Response\n");
}
#endregion
//return null;
}
}
}
PostAsXmlAsync will try to serialize the object passed to it. So you have a string that contains XML and then try to post the string as XML(Double serialization).
Use StringContent, giving it the XML string value and set the content type to appropriate media type, then post it. i.e. client.PostAsync(url, content)
using (var client = new System.Net.Http.HttpClient()) {
var url = "http://server:8100/api/SoftwareProductBuild";
var content = new StringContent(xmlcontent, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode) {
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine("Printing DEV Pool Response\n");
Console.WriteLine(responseBody);
} else {
Console.WriteLine(string.Format("Bad Response {0} \n", response.StatusCode.ToString()));
}
}
I created the Wep API in ASP.Net core to return the PDF. Here is my code:
public HttpResponseMessage Get(int id)
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var stream = new System.IO.FileStream(#"C:\Users\shoba_eswar\Documents\REquest.pdf", System.IO.FileMode.Open);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "NewTab";
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
return response;
}
But it returns only the JSON response:
{
"version":{
"major":1,
"minor":1,
"build":-1,
"revision":-1,
"majorRevision":-1,
"minorRevision":-1
},
"content":{
"headers":[
{
"key":"Content-Disposition",
"value":[
"attachment; filename=NewTab"
]
},
{
"key":"Content-Type",
"value":[
"application/pdf"
]
}
]
},
"statusCode":200,
"reasonPhrase":"OK",
"headers":[
],
"requestMessage":null,
"isSuccessStatusCode":true
}
Am I doing anything wrong here?
As explained in ASP.NET Core HTTPRequestMessage returns strange JSON message, ASP.NET Core does not support returning an HttpResponseMessage (what package did you install to get access to that type?).
Because of this, the serializer is simply writing all public properties of the HttpResponseMessage to the output, as it would with any other unsupported response type.
To support custom responses, you must return an IActionResult-implementing type. There's plenty of those. In your case, I'd look into the FileStreamResult:
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open);
return new FileStreamResult(stream, "application/pdf");
}
Or simply use a PhysicalFileResult, where the stream is handled for you:
public IActionResult Get(int id)
{
return new PhysicalFileResult(#"path\to\file", "application/pdf");
}
Of course all of this can be simplified using helper methods, such as Controller.File():
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open);
return File(stream, "application/pdf", "FileDownloadName.ext");
}
This simply abstracts the creation of a FileContentResult or FileStreamResult (for this overload, the latter).
Or if you're converting an older MVC or Web API application and don't want to convert all your code at once, add a reference to WebApiCompatShim (NuGet) and wrap your current code in a ResponseMessageResult:
public IActionResult Get(int id)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = ...
response.Content...
return new ResponseMessageResult(response);
}
If you don't want to use return File(fileName, contentType, fileDownloadName), then the FileStreamResult doesn't support setting the content-disposition header from the constructor or through properties.
In that case you'll have to add that response header to the response yourself before returning the file result:
var contentDisposition = new ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName("foo.txt");
Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
I couldn't comment the answer by CodeCaster since my reputation isn't high enough.
When trying
public IActionResult Get(int id)
{
using (var stream = new FileStream(#"path\to\file", FileMode.Open))
{
return File(stream, "application/pdf", "FileDownloadName.ext");
}
}
we got a
ObjectDisposedException: Cannot access a disposed object. Object name:
'Cannot access a closed file.'. System.IO.FileStream.BeginRead(byte[]
array, int offset, int numBytes, AsyncCallback callback, object state)
We removed the using
[HttpGet]
[Route("getImageFile")]
public IActionResult GetWorkbook()
{
var stream = new FileStream(#"pathToFile", FileMode.Open);
return File(stream, "image/png", "image.png");
}
And that worked. This is ASP.NET Core 2.1 running in IIS Express.
I don't have enough reputation to post this as a comment, so posting as an answer. The first 3 solutions from #CodeCaster and the solution from #BernhardMaertl are correct.
However, for someone who may not work with files often (like me), please note that if the process running this code (e.g. the API) only has read permissions to the file, you will need to specify that as the third parameter when creating your FileStream, otherwise the default behavior is to open the file for read/write and you will get an exception since you do not have write permissions.
The 3rd solution from #CodeCaster would then look like this:
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open, FileAccess.Read);
return File(stream, "application/pdf", "FileDownloadName.ext");
}
I want my users to be able to post dates to an asp.net web api controller in uk format, such as 01/12/2012 (1st Dec 2012).
From what i can tell by default, only us format is accepted.
Can i change something somewhere so that UK format is the default? I tried changing the globalization setting in the web.config but this had no effect.
Paul
Done this using a custom model binder, which is slightly different to the model binders in MVC3:
public class DateTimeModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var date = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
if (String.IsNullOrEmpty(date))
return false;
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName));
try
{
bindingContext.Model = DateTime.Parse(date);
return true;
}
catch (Exception)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("\"{0}\" is invalid.", bindingContext.ModelName));
return false;
}
}
}
And in my Global.asax.cs file, add this line to tell the api to use this model binder for DateTime values:
GlobalConfiguration.Configuration.BindParameter(typeof(DateTime), new DateTimeModelBinder());
Here is the method in my api controller:
public IList<LeadsLeadRowViewModel> Get([ModelBinder]LeadsIndexViewModel inputModel)
My LeadsIndexViewModel class had several DateTime properties which were now all valid UK date times.
Well, I also wanted to solve this at a global level ... and tore out lots of hair in the process. It turns out there are no extension points in WebApi where one would hope to intercept the incoming form data and modify them as needed. Wouldn't that be nice. So, lacking just that, I dug as deep as I could into WebApi source code to see what I could come up with. I ended up reusing the class in charge of parsing form data to create the model. I added just a few lines to deal specifically with dates. That class, below, can be added to the configuration like this:
private static void PlaceFormatterThatConvertsAllDatesToIsoFormat(HttpConfiguration config)
{
config.Formatters.Remove(
config.Formatters.FirstOrDefault(x => x is JQueryMvcFormUrlEncodedFormatter));
config.Formatters.Add(
new IsoDatesJQueryMvcFormUrlEncodedFormatter());
}
The formatter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http.ModelBinding;
namespace WebApi.Should.Allow.You.To.Set.The.Binder.Culture
{
// This class replaces WebApi's JQueryMvcFormUrlEncodedFormatter
// to support JQuery schema on FormURL. The reasong for this is that the
// supplied class was unreliable when parsing dates european style.
// So this is a painful workaround ...
/// <remarks>
/// Using this formatter any string that can be parsed as a date will be formatted using ISO format
/// </remarks>
public class IsoDatesJQueryMvcFormUrlEncodedFormatter : FormUrlEncodedMediaTypeFormatter
{
// we *are* in Israel
private static readonly CultureInfo israeliCulture = new CultureInfo("he-IL");
public override bool CanReadType(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
return true;
}
public override Task<object> ReadFromStreamAsync(Type type
, Stream readStream
, HttpContent content
, IFormatterLogger formatterLogger)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (readStream == null)
{
throw new ArgumentNullException("readStream");
}
// For simple types, defer to base class
if (base.CanReadType(type))
{
return base.ReadFromStreamAsync(type, readStream, content, formatterLogger);
}
var result = base.ReadFromStreamAsync(
typeof(FormDataCollection),
readStream,
content,
formatterLogger);
Func<object, object> cultureSafeTask = (state) =>
{
var innterTask = (Task<object>)state;
var formDataCollection = (FormDataCollection)innterTask.Result;
var modifiedCollection = new List<KeyValuePair<string, string>>();
foreach (var item in formDataCollection)
{
DateTime date;
var isDate =
DateTime.TryParse(item.Value,
israeliCulture,
DateTimeStyles.AllowWhiteSpaces,
out date);
if (true == isDate)
{
modifiedCollection.Add(
new KeyValuePair<string, string>(
item.Key,
date.ToString("o")));
}
else
{
modifiedCollection.Add(
new KeyValuePair<string, string>(
item.Key,
item.Value));
}
}
formDataCollection = new FormDataCollection(modifiedCollection);
try
{
return
formDataCollection.ReadAs(type, String.Empty, RequiredMemberSelector, formatterLogger);
}
catch (Exception e)
{
if (formatterLogger == null)
{
throw;
}
formatterLogger.LogError(String.Empty, e);
return GetDefaultValueForType(type);
}
};
return
Task.Factory.StartNew(
cultureSafeTask
, result);
}
}
}
There's an example of localising the jQuery date picker to en-gb here: http://weblogs.asp.net/hajan/archive/2010/10/05/integration-of-jquery-datepicker-in-asp-net-website-localization-part-3.aspx
Also, I see you tried setting the culture to en-GB, but I don't know if you tried setting the UI Culture as well in Web.Config (and I don't know if this affects the jQuery bundles in MVC or not):
<globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="en-GB" uiCulture="en-GB"/>
... or if that doesn't work (heh), why not pass the value as a string and then parse it in your api controller:
using System.Globalization;
// fetch the en-GB culture
CultureInfo ukCulture = new CultureInfo("en-GB");
// pass the DateTimeFormat information to DateTime.Parse
DateTime myDateTime = DateTime.Parse("StringValue" ,ukCulture.DateTimeFormat);
I need to get a response back in plain text from a ASP.NET Web API controller.
I have tried do a request with Accept: text/plain but it doesn't seem to do the trick.
Besides, the request is external and out of my control. What I would accomplish is to mimic the old ASP.NET way:
context.Response.ContentType = "text/plain";
context.Response.Write("some text);
Any ideas?
EDIT, solution:
Based on Aliostad's answer, I added the WebAPIContrib text formatter, initialized it in the Application_Start:
config.Formatters.Add(new PlainTextFormatter());
and my controller ended up something like:
[HttpGet, HttpPost]
public HttpResponseMessage GetPlainText()
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, "Test data", "text/plain");
}
Hmmm... I don't think you need to create a custom formatter to make this work. Instead return the content like this:
[HttpGet]
public HttpResponseMessage HelloWorld()
{
string result = "Hello world! Time is: " + DateTime.Now;
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new StringContent(result, System.Text.Encoding.UTF8, "text/plain");
return resp;
}
This works for me without using a custom formatter.
If you explicitly want to create output and override the default content negotiation based on Accept headers you won't want to use Request.CreateResponse() because it forces the mime type.
Instead explicitly create a new HttpResponseMessage and assign the content manually. The example above uses StringContent but there are quite a few other content classes available to return data from various .NET data types/structures.
For .net core:
[HttpGet("About")]
public ContentResult About()
{
return Content("About text");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/formatting
If you are just looking for a simple plain/text formatter without adding additional dependencies, this should do the trick.
public class TextPlainFormatter : MediaTypeFormatter
{
public TextPlainFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
public override bool CanWriteType(Type type)
{
return type == typeof(string);
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
{
return Task.Factory.StartNew(() => {
StreamWriter writer = new StreamWriter(stream);
writer.Write(value);
writer.Flush();
});
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
{
return Task.Factory.StartNew(() => {
StreamReader reader = new StreamReader(stream);
return (object)reader.ReadToEnd();
});
}
}
Don't forget to add it to your Global web api config.
config.Formatters.Add(new TextPlainFormatter());
Now you can pass string objects to
this.Request.CreateResponse(HttpStatusCode.OK, "some text", "text/plain");
Please be careful not to use context in ASP.NET Web API or you will sooner or later be sorry. Asynchronous nature of ASP.NET Web API makes using HttpContext.Current a liability.
Use a plain text formatter and add to your formatters. There are dozens of them around. You could even write yours easily. WebApiContrib has one.
You can force it by setting the content type header on httpResponseMessage.Headers to text/plain in your controller provided you have registered plain text formatter.
When Accept: text/plain doesnt work, then there is no registered formatter for text mime types.
You can ensure that there is no formatters for specified mime type by getting list of all supported formatters from service configuration.
Create a very straightforward media type formatter that support text mime types.
http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
An extension like the following one can reduce the number of lines and beautify your code:
public static class CommonExtensions
{
public static HttpResponseMessage ToHttpResponseMessage(this string str)
{
var resp = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(str, System.Text.Encoding.UTF8, "text/plain")
};
return resp;
}
}
Now you can consume the defined extension in your Web API:
public class HomeController : ApiController
{
[System.Web.Http.HttpGet]
public HttpResponseMessage Index()
{
return "Salam".ToHttpResponseMessage();
}
}
By routing {DOMAIN}/api/Home/Index you can see the following plain text:
MyPlainTextResponse
What is the best way to return XML from a controller's action in ASP.NET MVC? There is a nice way to return JSON, but not for XML. Do I really need to route the XML through a View, or should I do the not-best-practice way of Response.Write-ing it?
return this.Content(xmlString, "text/xml");
Use MVCContrib's XmlResult Action.
For reference here is their code:
public class XmlResult : ActionResult
{
private object objectToSerialize;
/// <summary>
/// Initializes a new instance of the <see cref="XmlResult"/> class.
/// </summary>
/// <param name="objectToSerialize">The object to serialize to XML.</param>
public XmlResult(object objectToSerialize)
{
this.objectToSerialize = objectToSerialize;
}
/// <summary>
/// Gets the object to be serialized to XML.
/// </summary>
public object ObjectToSerialize
{
get { return this.objectToSerialize; }
}
/// <summary>
/// Serialises the object that was passed into the constructor to XML and writes the corresponding XML to the result stream.
/// </summary>
/// <param name="context">The controller context for the current request.</param>
public override void ExecuteResult(ControllerContext context)
{
if (this.objectToSerialize != null)
{
context.HttpContext.Response.Clear();
var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType());
context.HttpContext.Response.ContentType = "text/xml";
xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
}
}
}
If you're building the XML using the excellent Linq-to-XML framework, then this approach will be helpful.
I create an XDocument in the action method.
public ActionResult MyXmlAction()
{
// Create your own XDocument according to your requirements
var xml = new XDocument(
new XElement("root",
new XAttribute("version", "2.0"),
new XElement("child", "Hello World!")));
return new XmlActionResult(xml);
}
This reusable, custom ActionResult serialises the XML for you.
public sealed class XmlActionResult : ActionResult
{
private readonly XDocument _document;
public Formatting Formatting { get; set; }
public string MimeType { get; set; }
public XmlActionResult(XDocument document)
{
if (document == null)
throw new ArgumentNullException("document");
_document = document;
// Default values
MimeType = "text/xml";
Formatting = Formatting.None;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = MimeType;
using (var writer = new XmlTextWriter(context.HttpContext.Response.OutputStream, Encoding.UTF8) { Formatting = Formatting })
_document.WriteTo(writer);
}
}
You can specify a MIME type (such as application/rss+xml) and whether the output should be indented if you need to. Both properties have sensible defaults.
If you need an encoding other than UTF8, then it's simple to add a property for that too.
If you are only interested to return xml through a request, and you have your xml "chunk", you can just do (as an action in your controller):
public string Xml()
{
Response.ContentType = "text/xml";
return yourXmlChunk;
}
There is a XmlResult (and much more) in MVC Contrib. Take a look at http://www.codeplex.com/MVCContrib
I've had to do this recently for a Sitecore project which uses a method to create an XmlDocument from a Sitecore Item and its children and returns it from the controller ActionResult as a File. My solution:
public virtual ActionResult ReturnXml()
{
return File(Encoding.UTF8.GetBytes(GenerateXmlFeed().OuterXml), "text/xml");
}
use one of these methods
public ContentResult GetXml()
{
string xmlString = "your xml data";
return Content(xmlString, "text/xml");
}
or
public string GetXml()
{
string xmlString = "your xml data";
Response.ContentType = "text/xml";
return xmlString;
}
Finally manage to get this work and thought I would document how here in the hopes of saving others the pain.
Environment
VS2012
SQL Server 2008R2
.NET 4.5
ASP.NET MVC4 (Razor)
Windows 7
Supported Web Browsers
FireFox 23
IE 10
Chrome 29
Opera 16
Safari 5.1.7 (last one for Windows?)
My task was on a ui button click, call a method on my Controller (with some params) and then have it return an MS-Excel XML via an xslt transform. The returned MS-Excel XML would then cause the browser to popup the Open/Save dialog. This had to work in all the browsers (listed above).
At first I tried with Ajax and to create a dynamic Anchor with the "download" attribute for the filename,
but that only worked for about 3 of the 5 browsers(FF, Chrome, Opera) and not for IE or Safari.
And there were issues with trying to programmatically fire the Click event of the anchor to cause the actual "download".
What I ended up doing was using an "invisible" IFRAME and it worked for all 5 browsers!
So here is what I came up with:
[please note that I am by no means an html/javascript guru and have only included the relevant code]
HTML (snippet of relevant bits)
<div id="docxOutput">
<iframe id="ifOffice" name="ifOffice" width="0" height="0"
hidden="hidden" seamless='seamless' frameBorder="0" scrolling="no"></iframe></div>
JAVASCRIPT
//url to call in the controller to get MS-Excel xml
var _lnkToControllerExcel = '#Url.Action("ExportToExcel", "Home")';
$("#btExportToExcel").on("click", function (event) {
event.preventDefault();
$("#ProgressDialog").show();//like an ajax loader gif
//grab the basket as xml
var keys = GetMyKeys();//returns delimited list of keys (for selected items from UI)
//potential problem - the querystring might be too long??
//2K in IE8
//4096 characters in ASP.Net
//parameter key names must match signature of Controller method
var qsParams = [
'keys=' + keys,
'locale=' + '#locale'
].join('&');
//The element with id="ifOffice"
var officeFrame = $("#ifOffice")[0];
//construct the url for the iframe
var srcUrl = _lnkToControllerExcel + '?' + qsParams;
try {
if (officeFrame != null) {
//Controller method can take up to 4 seconds to return
officeFrame.setAttribute("src", srcUrl);
}
else {
alert('ExportToExcel - failed to get reference to the office iframe!');
}
} catch (ex) {
var errMsg = "ExportToExcel Button Click Handler Error: ";
HandleException(ex, errMsg);
}
finally {
//Need a small 3 second ( delay for the generated MS-Excel XML to come down from server)
setTimeout(function () {
//after the timeout then hide the loader graphic
$("#ProgressDialog").hide();
}, 3000);
//clean up
officeFrame = null;
srcUrl = null;
qsParams = null;
keys = null;
}
});
C# SERVER-SIDE (code snippet)
#Drew created a custom ActionResult called XmlActionResult which I modified for my purpose.
Return XML from a controller's action in as an ActionResult?
My Controller method (returns ActionResult)
passes the keys parameter to a SQL Server stored proc that generates an XML
that XML is then transformed via xslt into an MS-Excel xml (XmlDocument)
creates instance of the modified XmlActionResult and returns it
XmlActionResult result = new XmlActionResult(excelXML, "application/vnd.ms-excel");
string version = DateTime.Now.ToString("dd_MMM_yyyy_hhmmsstt");
string fileMask = "LabelExport_{0}.xml";
result.DownloadFilename = string.Format(fileMask, version);
return result;
The main modification to the XmlActionResult class that #Drew created.
public override void ExecuteResult(ControllerContext context)
{
string lastModDate = DateTime.Now.ToString("R");
//Content-Disposition: attachment; filename="<file name.xml>"
// must set the Content-Disposition so that the web browser will pop the open/save dialog
string disposition = "attachment; " +
"filename=\"" + this.DownloadFilename + "\"; ";
context.HttpContext.Response.Clear();
context.HttpContext.Response.ClearContent();
context.HttpContext.Response.ClearHeaders();
context.HttpContext.Response.Cookies.Clear();
context.HttpContext.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);// Stop Caching in IE
context.HttpContext.Response.Cache.SetNoStore();// Stop Caching in Firefox
context.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero);
context.HttpContext.Response.CacheControl = "private";
context.HttpContext.Response.Cache.SetLastModified(DateTime.Now.ToUniversalTime());
context.HttpContext.Response.ContentType = this.MimeType;
context.HttpContext.Response.Charset = System.Text.UTF8Encoding.UTF8.WebName;
//context.HttpContext.Response.Headers.Add("name", "value");
context.HttpContext.Response.Headers.Add("Last-Modified", lastModDate);
context.HttpContext.Response.Headers.Add("Pragma", "no-cache"); // HTTP 1.0.
context.HttpContext.Response.Headers.Add("Expires", "0"); // Proxies.
context.HttpContext.Response.AppendHeader("Content-Disposition", disposition);
using (var writer = new XmlTextWriter(context.HttpContext.Response.OutputStream, this.Encoding)
{ Formatting = this.Formatting })
this.Document.WriteTo(writer);
}
That was basically it.
Hope it helps others.
A simple option that will let you use streams and all that is return File(stream, "text/xml");.
Here is a simple way of doing it:
var xml = new XDocument(
new XElement("root",
new XAttribute("version", "2.0"),
new XElement("child", "Hello World!")));
MemoryStream ms = new MemoryStream();
xml.Save(ms);
return File(new MemoryStream(ms.ToArray()), "text/xml", "HelloWorld.xml");
A small variation of the answer from Drew Noakes that use the method Save() of XDocument.
public sealed class XmlActionResult : ActionResult
{
private readonly XDocument _document;
public string MimeType { get; set; }
public XmlActionResult(XDocument document)
{
if (document == null)
throw new ArgumentNullException("document");
_document = document;
// Default values
MimeType = "text/xml";
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = MimeType;
_document.Save(context.HttpContext.Response.OutputStream)
}
}