I'm sending a request to server in the following form:
http://localhost:12345/api/controller/par1/par2
The request is correctly resolved to a method like:
[HttpPost]
public void object Post(string par1, string par2)
However, I pass additional data through the request content. How can I retrieve these data?
For the sake of example, let's say, that the request is sent from the form:
<form action="http://localhost:12345/api/controller/par1/par2" method="post">
<input type="hidden" name="data" value="value" />
<input type="submit" name="submit" value="Submit" />
</form>
From answer in this question:
How to get Json Post Values with asp.net webapi
Autoparse using parameter binding; note that the dynamic is made up of JToken, hence the .Value accessor.
public void Post([FromBody]dynamic value) {
var x = value.var1.Value; // JToken
}
Read just like Request.RequestUri.ParseQueryString()[key]
public async Task Post() {
dynamic obj = await Request.Content.ReadAsAsync<JObject>();
var y = obj.var1;
}
Same as #2, just not asynchronously (?) so you can use it in a helper method
private T GetPostParam<T>(string key) {
var p = Request.Content.ReadAsAsync<JObject>();
return (T)Convert.ChangeType(p.Result[key], typeof(T)); // example conversion, could be null...
}
Caveat -- expects media-type application/json in order to trigger JsonMediaTypeFormatter handling.
After spending a good bit of time today trying to wrap my brain around the (significant but powerful) paradigm shift between old ways of processing web form data and how it is done with WebAPI, I thought I'd add my 2 cents to this discussion.
What I wanted to do (which is pretty common for web form processing of a POST) is to be able to grab any of the form values I want, in any order. Say like you can do if you have your data in a System.Collections.Specialized.NameValueCollection. But turns out, in WebAPI, the data from a POST comes back at you as a stream. So you can't directly do that.
But there is a cool little class named FormDataCollection (in System.Net.Http.Formatting) and what it will let you do is iterate through your collection once.
So I wrote a simple utility method that will run through the FormDataCollection once and stick all the values into a NameValueCollection. Once this is done, you can jump all around the data to your hearts content.
So in my ApiController derived class, I have a post method like this:
public void Post(FormDataCollection formData)
{
NameValueCollection valueMap = WebAPIUtils.Convert(formData);
... my code that uses the data in the NameValueCollection
}
The Convert method in my static WebAPIUtils class looks like this:
/// <summary>
/// Copy the values contained in the given FormDataCollection into
/// a NameValueCollection instance.
/// </summary>
/// <param name="formDataCollection">The FormDataCollection instance. (required, but can be empty)</param>
/// <returns>The NameValueCollection. Never returned null, but may be empty.</returns>
public static NameValueCollection Convert(FormDataCollection formDataCollection)
{
Validate.IsNotNull("formDataCollection", formDataCollection);
IEnumerator<KeyValuePair<string, string>> pairs = formDataCollection.GetEnumerator();
NameValueCollection collection = new NameValueCollection();
while (pairs.MoveNext())
{
KeyValuePair<string, string> pair = pairs.Current;
collection.Add(pair.Key, pair.Value);
}
return collection;
}
Hope this helps!
I had a problem with sending a request with multiple parameters.
I've solved it by sending a class, with the old parameters as properties.
<form action="http://localhost:12345/api/controller/method" method="post">
<input type="hidden" name="name1" value="value1" />
<input type="hidden" name="name2" value="value2" />
<input type="submit" name="submit" value="Submit" />
</form>
Model class:
public class Model {
public string Name1 { get; set; }
public string Name2 { get; set; }
}
Controller:
public void method(Model m) {
string name = m.Name1;
}
It is hard to handle multiple parameters on the action directly. The better way to do it is to create a view model class. Then you have a single parameter but the parameter contains multiple data properties.
public class MyParameters
{
public string a { get; set; }
public string b { get; set; }
}
public MyController : ApiController
{
public HttpResponseMessage Get([FromUri] MyParameters parameters) { ... }
}
Then you go to:
http://localhost:12345/api/MyController?a=par1&b=par2
Reference: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
If you want to use "/par1/par2", you can register an asp routing rule. eg routeTemplate: "API/{controller}/{action}/{a}/{b}".
See http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Try this.
public string Post(FormDataCollection form) {
string par1 = form.Get("par1");
// ...
}
It works for me with webapi 2
None of the answers here worked for me. Using FormDataCollection in the post method seems like the right answer but something about my post request was causing webapi to choke. eventually I made it work by including no parameters in the method call and just manually parsing out the form parameters like this.
public HttpResponseMessage FileUpload() {
System.Web.HttpRequest httpRequest = System.Web.HttpContext.Current.Request;
System.Collections.Specialized.NameValueCollection formData = httpRequest.Form;
int ID = Convert.ToInt32(formData["ID"]);
etc
I found for my use case this was much more useful, hopefully it helps someone else that spent time on this answer applying it
public IDictionary<string, object> GetBodyPropsList()
{
var contentType = Request.Content.Headers.ContentType.MediaType;
var requestParams = Request.Content.ReadAsStringAsync().Result;
if (contentType == "application/json")
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<IDictionary<string, object>>(requestParams);
}
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Is there a way to handle form post data in a Web Api controller?
The normal approach in ASP.NET Web API is to represent the form as a model so the media type formatter deserializes it. Alternative is to define the actions's parameter as NameValueCollection:
public void Post(NameValueCollection formData)
{
var value = formData["key"];
}
ON WEB API.
[HttpGet]
[Route("api/Get_EXCUTA_PROCEDURE_IBESVNDACMVDD")]
public IHttpActionResult Get(int CodigoPuxada...)
{
string retornoErro = string.Empty;
try
{
//int codigoPuxada = entrada.CodigoPuxada;
SetKeyAtual(CodigoPuxada);
var repo = new ItemBroker_Dim_Canal_BookRepositorio(ConnectionString);
try
{
var dadosRetorno = repo.ExcuteProcedure_Busca_vbc_(CodigoPuxada,...); // method return object (dataset)
return Ok(dadosRetorno);
}
catch
{
throw;
}
}
catch (Exception ex)
{
retornoErro = ex.Message;
if (ex.InnerException != null)
retornoErro = ex.InnerException.ToString();
}
return Ok(retornoErro);
}
Other projet invoke web api...
(USING RESTSHARP DLL)
RestClient clientHttpPost1 = null;
string dadosVbc123 = string.empty;
clientHttpPost1 = new RestSharp.RestClient($"{urlWebApiAvante}Get_EXCUTA_PROCEDURE_IBESVNDACMVDD?CodigoPuxada=...");
RestSharp.RestRequest request2 = new RestSharp.RestRequest(RestSharp.Method.GET);
request2.RequestFormat = RestSharp.DataFormat.Json;
request2.AddHeader("Content-Type", "application/json;charset=utf-8");
string strAux1 = string.Empty;
request2.Timeout = 180000;
RestSharp.IRestResponse response = clientHttpPost1.Execute(request2);
if ((response != null) && response.StatusCode == System.Net.HttpStatusCode.OK)
{
try
{
var dataObjects = response.Content.ToString().Trim();
dadosVbc123 = dataObjects.ToString().Replace("\t", "");
if (dadosVbc123.Trim() == "{\"IBESVNDACMVDD\":[]}")
dadosVbc123 = string.Empty;
}
...
}
// converting JSON to dataset
string val1 = dadosVbc123.Replace("{\"IBESVNDACMVDD\":", "").Replace("}]}", "}]");
DataTable dtVBC123 = (DataTable)Newtonsoft.Json.JsonConvert.DeserializeObject(val1, (typeof(DataTable)));
Related
I've got a checkout page that is supposed to redirect user to the payment-gateway payment processing page. It expects form data to be posted (Amount, Access_key, Order_id, etc;).
What I've gotten so far is this;
public class OrderController : Controller {
private readonly IOrderService _service;
public OrderController(IOrderService service) {
_service = service;
}
[HttpGet]
public async Task<IActionResult> Index () {
// order processing form
// order model binding code not included for brevity.
return View();
}
[HttpPost]
public async Task<IActionResult> ProcessOrder (OrderModel model) {
var orderId = _service.CreateOrder(model);
// order processing code not included for brevity. (basically create new order and reduce stock)
var dataToPost = new PGData() {
Amount = 10,
Currency = USD,
TxId = orderId,
SuccessUrl = "https://test#domain.com/order/success",
FailedUrl = "https://test#domain.com/order/fail"
Access_Key = "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
Field_Hash = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
// need to redirect user to ("https://pg#pgdomain.com/paynow") with the post data above
}
}
I've found a code snippet that works with .Net Framework but not with .Net Core:
private class RemotePost
{
private NameValueCollection Content = new NameValueCollection();
public string Url = "";
public string Method = "post";
public string FormName = "form1";
public void Add(string name, string value)
{
Content.Add(name, value);
}
public void Post()
{
HttpContext.Response.Body.WriteAsync(bytes);
System.Web.HttpContext.Current.Response.Clear();
System.Web.HttpContext.Current.Response.Write("<html><head>");
System.Web.HttpContext.Current.Response.Write(string.Format("</head><body onload=\"document.{0}.submit()\">", FormName));
System.Web.HttpContext.Current.Response.Write(string.Format("<form name=\"{0}\" method=\"{1}\" action=\"{2}\" >", FormName, Method, Url));
for (int i = 0; i < Inputs.Keys.Count; i++)
{
System.Web.HttpContext.Current.Response.Write(string.Format("<input name=\"{0}\" type=\"hidden\" value=\"{1}\">", Inputs.Keys[i], Inputs[Inputs.Keys[i]]));
}
System.Web.HttpContext.Current.Response.Write("</form>");
System.Web.HttpContext.Current.Response.Write("</body></html>");
System.Web.HttpContext.Current.Response.End();
}
}
How do I go about achieving this in .Net Core (3.1) in the most secure way as possible?
The ProcessOrder method creates the remote object for external destination (your dataToPost) and returns a view:
return View(dataToPost);
The ProcessOrder.cshtml view accepts a PGData model:
#model PGData
This view contains a form with hidden inputs for each property that needs to be sent to the external destination:
<form name="my-form" method="POST" action="https://pg#pgdomain.com/paynow">
<input type="hidden" asp-for="#Model.Amount"/>
...
</form>
A piece of JavaScript code will submit this form on document load:
window.onload = function(){
document.forms['my-form'].submit();
}
Usually the payment gateways call a callback endpoint in your website and you can check the status of payment and if the data was tampered with.
By the way you need to read the documentation of your gateway endpoint carefully since you're dealing with money here. They usually provide example code for popular web frameworks.
We have a fun situation where we are storing json as a string in SQL Server. We don't not care what is in this object its pretty much a passthrough property. Passthrough meaning we just save it for clients and return it as is. We never read it in C#. I'm storing it as a nvarchar in the database but I'm trying to figure out how i can automagically serialize that string into a json object to return to the client. I dont want to have to in javascript call fromJson.
We are using Newtonsoft as our Json Serializer. Here is the highlevel setup:
DTO:
public class MyDto{
public dynamic SessionBag { get;set;}
}
Entity Framework Entity:
public class My{
public string SessionBag { get;set;}
}
A client would post/put us:
{"SessionBag":{"Name":"test"}}
We would then save it in the db as a string:
"{"Name":"test"}"
How can I serialize this so when it returns from Web.API it looks like:
{
SessionBag:{
Name: "test"
}
}
I'm currently messing around trying to get it to save using dynamic / object. But i can't figure how how to return it as a json object. I would love to figure out how to do this with just annotations.
Here is how I convert it to a string to save:
if (dto.SessionBag != null){
var serializer = JsonSerializer.Create(new JsonSerializerSettings(){
NullValueHandling = NullValueHandling.Ignore
});
using (var writer = new StringWriter()){
serializer.Serialize(writer, dto.SessionBag);
entity.SessionData = writer.ToString();
}
}
In case its helpful our WebApiControllers are pretty simple and just return an IHttpActionResult with the dto. All feedback is welcome.
So I think i figured it out. In my dto:
[JsonIgnore]
public string SessionBagString { get; set; }
public JObject SessionBag
{
get
{
if (!string.IsNullOrEmpty(SessionBagString))
{
return JObject.Parse(SessionBagString);
}
return null;
}
set
{
if(value != null)
{
SessionBagString = value.ToString();
}
}
}
In my repo code I now have:
if (dto.SessionBag != null)
{
entity.SessionBagString = dto.SessionBagString;
}
That pretty much worked for me. Let me know if there is a better way to do it.
this opportunity Ii'd like to thank everyone who has an answer to this question, I'm trying to get a json from my web api service and I can't this is my code at the web api...
[ResponseType(typeof(List<CompanyType>))]
[Route("GetList")]
[DeflateCompression]
public async Task<IHttpActionResult> Get()
{
List<CompanyType> companyTypes = (List<CompanyType>)MemoryCacheManager.GetValue(#"CompanyTypes");
if (companyTypes != null) return Ok(companyTypes);
companyTypes = await _CompanyType.Queryable().ToListAsync();
if (companyTypes == null) return Ok(HttpStatusCode.NoContent);
MemoryCacheManager.Add(#"CompanyTypes", companyTypes);
return Ok(companyTypes);
}
and at the site of my client I got this
public async Task<T> GetAsync<T>(string action, string authToken = null)
{
using (var client = new HttpClient())
{
if (!authToken.IsNullOrWhiteSpace())
client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(#"Bearer " + authToken);
var result = await client.GetAsync(BuildActionUri(action));
string json = await result.Content.ReadAsStringAsync();
if (result.IsSuccessStatusCode)
return JsonConvert.DeserializeObject<T>(json); //This line fails because the characters in the value
throw new ApiException(result.StatusCode, json);
}
}
As you can see there nothing than weird here it is a simple code that try to parse a json value to a Generic class but this fails bacause when i call my webapi Url it gives me this value
json = ��VR�LQ�R2T�Q�2u�B*R�"E�e�)�#q�������̔Ԣb%��Ҝ�Z�>#�>#�>�̊B������J�r2�q�
I don't know why my webapi give me tha value, when I try to debug just my service it give me this value
<ArrayOfCompanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" ><CompanyType z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"><Id>1</Id><Type>Privada</Type><JobProviders i:nil="true" /></CompanyType><CompanyType z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"><Id>2</Id><Type>Mixta</Type><JobProviders i:nil="true" /></CompanyType><CompanyType z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"><Id>3</Id><Type>Publica</Type><JobProviders i:nil="true" /></CompanyType></ArrayOfCompanyType>
as you can see everything looks fine but the problem start when I try to parse to get this value from my client.
this is my class
[DataContract(IsReference = true, Name = #"CompanyType", )]
public class CompanyType : Entity
{
[DataMember(Order = 0)]
public int Id { get; set; }
[DataMember(Order = 1)]
public string Type { get; set; }
[DataMember(Order = 2)]
public virtual List<JobProvider> JobProviders { get; set; }
}
I tried it without de DataContracts and still the same error.
best regards!.
Well I figured out, I was trying to use this sample http://blog.developers.ba/asp-net-web-api-gzip-compression-actionfilter/ but the thing was that the serializer wasn´t registered, so at my startup project I did this
GlobalConfiguration.Configuration.Formatters.Add(new ProtoBufFormatter());
And I changed my Actions deleting the attribute [DeflateCompression]
[ResponseType(typeof(List<CompanyType>))]
[Route("GetList")]
//[DeflateCompression]
public async Task<IHttpActionResult> Get()
{
Logic goes here....
}
And It's working now, but now I have another Issue and its when I try to make a call to my webapi action where I have to response an IHttpActionResult there is an exception of
no serializer defined for type: System.Object
i'm making a request do a asp.net webapi Post Method, and i'm not beeing able to get a request variable.
Request
jQuery.ajax({ url: sURL, type: 'POST', data: {var1:"mytext"}, async: false, dataType: 'json', contentType: 'application/x-www-form-urlencoded; charset=UTF-8' })
.done(function (data) {
...
});
WEB API Fnx
[AcceptVerbs("POST")]
[ActionName("myActionName")]
public void DoSomeStuff([FromBody]dynamic value)
{
//first way
var x = value.var1;
//Second way
var y = Request("var1");
}
i Cannot obtain the var1 content in both ways... (unless i create a class for that)
how should i do that?
First way:
public void Post([FromBody]dynamic value)
{
var x = value.var1.Value; // JToken
}
Note that value.Property actually returns a JToken instance so to get it's value you need to call value.Property.Value.
Second way:
public async Task Post()
{
dynamic obj = await Request.Content.ReadAsAsync<JObject>();
var y = obj.var1;
}
Both of the above work using Fiddler. If the first option isn't working for you, try setting the content type to application/json to ensure that the JsonMediaTypeFormatter is used to deserialize the content.
After banging my head around for a while on this and trying many different things I ended up putting some breakpoints on the API server and found the key value pairs stuffed down in the request. After I knew where they were, it was easy to access them. However, I have only found this method to work with WebClient.UploadString. However, it does work easily enough and allows you to load up as many parameters as you like and very easily access them server side. Note that I am targeting .net 4.5.
CLIENT SIDE
// Client request to POST the parameters and capture the response
public string webClientPostQuery(string user, string pass, string controller)
{
string response = "";
string parameters = "u=" + user + "&p=" + pass; // Add all parameters here.
// POST parameters could also easily be passed as a string through the method.
Uri uri = new Uri("http://localhost:50000/api/" + controller);
// This was written to work for many authorized controllers.
using (WebClient wc = new WebClient())
{
try
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
response = wc.UploadString(uri, login);
}
catch (WebException myexp)
{
// Do something with this exception.
// I wrote a specific error handler that runs on the response elsewhere so,
// I just swallow it, not best practice, but I didn't think of a better way
}
}
return response;
}
SERVER SIDE
// In the Controller method which handles the POST request, call this helper:
string someKeyValue = getFormKeyValue("someKey");
// This value can now be used anywhere in the Controller.
// Do note that it could be blank or whitespace.
// This method just gets the first value that matches the key.
// Most key's you are sending only have one value. This checks that assumption.
// More logic could be added to deal with multiple values easily enough.
public string getFormKeyValue(string key)
{
string[] values;
string value = "";
try
{
values = HttpContext.Current.Request.Form.GetValues(key);
if (values.Length >= 1)
value = values[0];
}
catch (Exception exp) { /* do something with this */ }
return value;
}
For more info on how to handle multi-value Request.Form Key/Value pairs, see:
http://msdn.microsoft.com/en-us/library/6c3yckfw(v=vs.110).aspx
I searched all morning to find an answer that depicted both client and server code, then finally figured it out.
Brief intro - The UI is an MVC 4.5 project that implements a standard view. The server side is an MVC 4.5 WebApi. The objective was to POST the model as JSON and subsequently update a database. It was my responsibility to code both the UI and backend. Below is the code. This worked for me.
Model
public class Team
{
public int Ident { get; set; }
public string Tricode { get; set; }
public string TeamName { get; set; }
public string DisplayName { get; set; }
public string Division { get; set; }
public string LogoPath { get; set; }
}
Client Side (UI Controller)
private string UpdateTeam(Team team)
{
dynamic json = JsonConvert.SerializeObject(team);
string uri = #"http://localhost/MyWebApi/api/PlayerChart/PostUpdateTeam";
try
{
WebRequest request = WebRequest.Create(uri);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(json);
streamWriter.Flush();
streamWriter.Close();
}
WebResponse response = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
}
catch (Exception e)
{
msg = e.Message;
}
}
Server Side (WebApi Controller)
[Route("api/PlayerChart/PostUpdateTeam")]
[HttpPost]
public string PostUpdateTeam(HttpRequestMessage context)
{
var contentResult = context.Content.ReadAsStringAsync();
string result = contentResult.Result;
Team team = JsonConvert.DeserializeObject<Team>(result);
//(proceed and update database)
}
WebApiConfig (route)
config.Routes.MapHttpRoute(
name: "PostUpdateTeam",
routeTemplate: "api/PlayerChart/PostUpdateTeam/{context}",
defaults: new { context = RouteParameter.Optional }
);
Try this.
public string Post(FormDataCollection form) {
string par1 = form.Get("par1");
// ...
}
try using following way
[AcceptVerbs("POST")]
[ActionName("myActionName")]
public static void DoSomeStuff(var value)
{
//first way
var x = value;
}
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)
}
}