I am trying to convert SQL server database table into XML file. I followed this solution.
I have created this class as shown in solution
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();
XmlRootAttribute root = new XmlRootAttribute("response");
var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType(), root);
context.HttpContext.Response.ContentType = "text/xml";
xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
}
}
Instead of this:
public ActionResult GetStuffAsXml(int id)
{
var dbStuff = db.GetStuff(id);
// fetch stuff in database
return new XmlResult(dbStuff);
}
I have written this(my purpose is to get all products):
public ActionResult Transfer()
{
var product = from s in db.Product
select s;
// fetch stuff in database
return new XmlResult(product);
}
In debugging process, this error came out:
To be XML serializable, types which inherit from IEnumerable must have
an implementation of Add(System.Object) at all levels of their
inheritance hierarchy.
System.Data.Entity.Infrastructure.DbQuery`1[[Overstock.Models.Product,
Overstock, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
does not implement Add(System.Object).
Source error:
Line 42: var xs = new
System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType(),
root);
I assume that error is coming out because I am taking products in wrong way:
var product = from s in db.Product select s;
In what form I should send data to XmlResult class in order to convert SQL Server table to XML file format?
If 'db' is a DbContext in this method
var product = from s in db.Product
select s;
// fetch stuff in database
return new XmlResult(product);
Then you are not getting out a DataRow or DataTable, you're getting a collection of strongly typed classes. If you want make xml from them use this code:
public static string SerializeAsXml<T>(T element)
{
XmlSerializer xmlSerializer = new XmlSerializer(element.);
StringWriter textWriter = new StringWriter();
xmlSerializer.Serialize(textWriter, element.GetType());
return textWriter.ToString();
}
call it
var products = from s in db.Product
select s;
// fetch stuff in database
return SerializeAsXml(products);
Related
I'm working on an established site. Although small (in terms of pages), there are some big money landing pages as well as the usual stock pages.
Because the site was relatively small, the page structure was flat.
https://example.com/contact
https://example.com/big-money-page
We plan on introducing lots more pages with different page designs. This means we'll either use master pages and/or aspx templated pages and create our own database driven CMS.
Here's the problem I can see is with url routing:
Template type 1
Route url: /{Name} - e.g. /big-money-page
Physica path: ~/template1.aspx
Template type 2
Route url: /{Name} - e.g. /new-supporting-page
Physical path: ~/template2.aspx
I would like to make this work without disruption to the existing money pages and, if possible, keep the familiar website structure, as to visitors, template1 and template2 are similar pages and don't naturally reside in different folders - they just differ in design.
Also, fixed deep routed folder structures make it difficult to make changes in the future.
I've been using WF routing for some time but always in simple ways. Anyone know how I can make the changes work with limited consequences?
UPDATE --------------------------------------------------------------------
Okay, in the absence of any feedback, I've come up with an idea to put on the table. I'd appreciate feedback on the fesibility and any downsides that can be thought of.
My idea is to have a dummy route/page.
The route would take the form http://example.com/{name}.
The dummy page retrieves data from the database for the target page using the placeholder {name}.
We then server.transfer to the correct target page, using the data we retrieved from the database.
I think this will work but I'm concerned about the things I don't know:
Browser compatibility for server.transfer
Performance overhead
Impact on output caching
Other things that haven't even crossed my mind
Of course this is not an ideal solution but I'm also open to any other ideas.
In a WebForm project the task can be implemented using a custom HTTPModule. The implementation includes a few steps. A simplified version is as following:
1. SQL
create table dbo.UrlMap (
publicUrl varchar(255) not null primary key,
PhysUrl varchar(255) not null
)
Fill the table with some data like
publicUrl PhysUrl
big-money-page template1.aspx?id=1
huge-money-page template1.aspx?id=2
no-money-page template2.aspx?id=3
other-page template1.aspx?id=4
2. Create a class in the App_code folder
using System;
using System.Web;
/// <summary>
/// Implements IHttpModule with custom URLs
/// </summary>
public class UrlMap:IHttpModule
{
/// <summary>
/// Initialize the module
/// </summary>
/// <param name="context"></param>
void IHttpModule.Init(HttpApplication context)
{
context.BeginRequest += Context_BeginRequest;
context.PostMapRequestHandler += Context_PostMapRequestHandler;
}
private void Context_BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
Url2PhysPath(app.Request.Path, app);
}
private void Context_PostMapRequestHandler(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var pg = app.Context.Handler as System.Web.UI.Page;
if (pg != null)
{
pg.PreRenderComplete += Pg_PreRenderComplete;
}
}
private void Pg_PreRenderComplete(object sender, EventArgs e)
{
ProcessPageTree((System.Web.UI.Control)sender);
}
/// <summary>
/// Replaces physical URLs on the page with "beutified" version
/// </summary>
/// <param name="control"></param>
private void ProcessPageTree(System.Web.UI.Control control)
{
var form = control as System.Web.UI.HtmlControls.HtmlForm;
if (form != null)
{
form.Action = BeautifyUrl(form.Page.Request.Url.PathAndQuery);
}
//other types in a similar way
if (control.HasControls())
{
foreach(System.Web.UI.Control c in control.Controls)
{
ProcessPageTree(c);
}
}
}
/// <summary>
/// Helper function. Can be inlined.
/// Searches "beautified" url in a DB and rewrites path
/// </summary>
/// <param name="url"></param>
/// <param name="app"></param>
private static void Url2PhysPath(string url, HttpApplication app)
{
using (var cnn = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SiteCnn"].ConnectionString))
{
var cmd = new System.Data.SqlClient.SqlCommand("select physPath from dbo.urlMap where publicUrl=#url", cnn);
cmd.CommandType = System.Data.CommandType.Text;
cmd.Parameters.Add("#url", System.Data.SqlDbType.VarChar, 255).Value = url;
cnn.Open();
string physPath = null;
using(var r = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
if (r.Read())
{
physPath = (string)r["physPath"];
}
r.Close();
}
if (!string.IsNullOrEmpty(physPath))
{
app.Context.RewritePath("/" + physPath);
}
}
}
/// <summary>
/// Helper function
/// </summary>
/// <param name="physUrl"></param>
/// <returns>returns original url when nothing is found</returns>
private static string BeautifyUrl(string physUrl)
{
using (var cnn = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SiteCnn"].ConnectionString))
{
var cmd = new System.Data.SqlClient.SqlCommand("select publicUrl from dbo.urlMap where physPath=#url", cnn);
cmd.CommandType = System.Data.CommandType.Text;
cmd.Parameters.Add("#url", System.Data.SqlDbType.VarChar, 255).Value = physUrl;
cnn.Open();
string pubUrl = physUrl;//to return original url when nothing is found
using(var r = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
if (r.Read())
{
pubUrl = (string)r["publicUrl"];
}
r.Close();
}
return pubUrl;
}
}
/// <summary>
/// Required by interface
/// </summary>
void IHttpModule.Dispose()
{
// throw new NotImplementedException();
}
}
3. Modify Web.config
Register the module. Add following line to configuration \ system.webServer \ modules
<add name="UrlRewriter" type="UrlMap" preCondition="managedHandler"/>
Follow up
Browser compatibility for server.transfer not a issue. Browser receives HTML only
Performance overhead Not much
Impact on output caching Cached better then template.aspx?id=123
Other things that haven't even crossed my mind Both publicUrl and physUrl must be unique. In practice you can cache direct and inverted key lookups in static Dictionary<string, string> variables.
I'm trying to create an ASP.NET Custom Control that has uses ITemplate to allow the developer to place their own inner ASP/HTML controls in it. The purpose of the control is to parse the rendered HTML of the template contents and search/replace certain placeholder text that might appear (such as [Location], [Division], [FirstName]) with context specific values.
I'm struggling on how to "capture" the generated HTML and "replace" aspects of it as required prior to going out to the response object.
My thoughts are on the lines of using RenderContents(HtmlTextWriter output) to replace output with my own stream and get the controls to write to that. I can then do what I need to do with the rendered content before sending out to output.
However, I am unsure if this is the right method.
Here is a snippet of what I have:
/// <summary>
/// This custom control allows replacement fields to be picked up
/// </summary>
[DefaultProperty("Text")]
[ToolboxData("<{0}:ReplaceableText runat=\"server\" Text=\"[Text]\"></{0}:ReplaceableText>")]
public partial class ReplaceableText : WebControl
{
public ReplaceableText()
{
TokenReplacer = new TokenReplacer();
}
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate Content { get; set; }
/// <summary>
/// The component the performs the replacement
/// </summary>
/// <remarks>
/// An alternative TokenReplacer component could be installed by the caller if required.
/// More typically, you will use the AddResolver(s) methods to apply callbacks to do the
/// actual replacement.
/// </remarks>
public ITokenReplacer TokenReplacer { get; set; }
public bool HasTokenReplacer { get { return (TokenReplacer != null && !DesignMode); } }
protected override void RenderContents(HtmlTextWriter output)
{
System.Diagnostics.Debug.WriteLine("RenderContents() enter");
if (HasTokenReplacer)
{
using (var memStream = new MemoryStream())
{
using (var sw = new StreamWriter(memStream))
{
using (var htw = new HtmlTextWriter(sw))
{
base.RenderContents(htw);
}
}
// !! memStream is still empty (0 bytes) !!
var text = Encoding.UTF8.GetString(memStream.GetBuffer());
var text2 = TokenReplacer.Convert(text);
output.Write(text2);
}
}
else
{
base.RenderContents(output);
}
output.Write(ConvertedText);
System.Diagnostics.Debug.WriteLine("RenderContents() exit");
}
protected override void CreateChildControls()
{
if (Content != null)
{
Controls.Clear();
Content.InstantiateIn(this); // !!! I need this to write to my own alternative temporary stream
}
base.CreateChildControls();
}
Unfortunately, I don't seem to get any mark-up deposited into my Memory Stream.
Silly sausage that I am, I hadn't put any controls into my Contents ITemplate.
This solution does appear to work well, just make sure you check that Controls.Count > 0 if you find your MemoryStream ends up with 0 bytes.
I have several resource files, e.g.
default.aspx.resx, default.aspx.nl.resx, default.aspx.en.resx
Now when I'm on the Dutch domain the default.aspx.nl.resx is loaded.
But now I want to access the value from default.aspx.en.resx and get the English value belonging to name "title".
I can now achieve this by changing the current culture server-side, access the value and then change it back, like so:
Dim culture As CultureInfo = New CultureInfo("en")
Threading.Thread.CurrentThread.CurrentCulture = culture
Threading.Thread.CurrentThread.CurrentUICulture = culture
Dim title as String = GetLocalResourceObject("title")
culture = New CultureInfo("nl")
Threading.Thread.CurrentThread.CurrentCulture = culture
Threading.Thread.CurrentThread.CurrentUICulture = culture
But is there a better/faster way? Preferably without have to change the culture for the current thread, so I can just define which resource file I want to access and in which language?
You can add in parameter your targe culture
GetLocalResourceObject("title","YourCulture");
link : http://msdn.microsoft.com/fr-fr/library/ms149953.aspx
Edit: (Sorry I didn't know that you wanted another method different from this, but this was the only way that I managed to do:)
I managed to do this by doing:
var userLanguage = "en-US";
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(userLanguage);
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo(userLanguage);
HttpContext.GetGlobalResourceObject("MyAppResource", "KeyThatIWantToGet");
Where MyAppResource is how your .resx file is named and KeyThatIWantToGet explains itself.
When not using the HttpContext (general .NET applications) I use the following helper:
/// <summary>
/// Disposable class that lets us assume a specific culture while executing
/// a certain block of code. You'd typically use it like this:
///
/// using (new CultureContext("de"))
/// {
/// // Will return the German translation of "Please click here"
/// string text = SharedResource.Please_click_here;
/// }
/// </summary>
public class CultureContext : IDisposable
{
private readonly CultureInfo _previousCulture;
private readonly CultureInfo _previousUiCulture;
public CultureContext(CultureInfo culture)
{
// Save off the previous culture (we'll restore this on disposal)
_previousCulture = Thread.CurrentThread.CurrentCulture;
_previousUiCulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
public CultureContext(string cultureCode)
: this(new CultureInfo(cultureCode))
{
}
/// <summary>
/// Syntactic sugar so that we can switch to a culture context as follows:
///
/// using (CultureContext.For("de"))
/// {
/// // Will return the German translation of "Please click here"
/// string text = SharedResource.Please_click_here;
/// }
/// </summary>
public static CultureContext For(string cultureCode)
{
return new CultureContext(cultureCode);
}
public void Dispose()
{
// Restore the culture settings that were in place before switching
// to this context
Thread.CurrentThread.CurrentCulture = _previousCulture;
Thread.CurrentThread.CurrentUICulture = _previousUiCulture;
}
}
Using vanilla MVC I can revalidate my model with TryValidateModel. The TryValidateModel method doesn't seem to be applicable to WebAPI. How can I revalidate my model when using WebAPI?
I know it has been a while since this has been asked, but the problem is still valid. Thus i thought i should share my solution to this problem.
I decided to implement the TryValidateModel(object model) myself, based on the implementation in the System.Web.Mvc.Controller.cs
The problem is that the mvc's TryValidateModel internally used their own HttpContext and ModelState. If you go and compaire the two, they are very similar....
The be able to use our own HttpContext there exists a HttpContextWrapper that can be used for that.
And Since we have to clear our model state, it doesn't really matter that we use a different type of ModelState , as long as we get the desired result, thus i create a new ModelState object from the correct type...
I did add the error to the ModelState of the controller and not to the model state to the newly created ModelState , This seems to work just fine for me :)
Here is my code, that i just added to the controller...
do not forget to import the library...
using System.Web.ModelBinding;
protected internal bool TryValidateModel(object model)
{
return TryValidateModel(model, null /* prefix */);
}
protected internal bool TryValidateModel(object model, string prefix)
{
if (model == null)
{
throw new ArgumentNullException("model");
}
ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
var t = new ModelBindingExecutionContext(new HttpContextWrapper(HttpContext.Current), new System.Web.ModelBinding.ModelStateDictionary());
foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, t).Validate(null))
{
ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
}
return ModelState.IsValid;
}
I don't know when was it added but now there is Validate method on api controller.
ApiController.Validate Method (TEntity)
https://msdn.microsoft.com/en-us/library/dn573258%28v=vs.118%29.aspx
Based from rik-vanmechelen original answer, here is my version that relies on the services container exposed by Web API.
/// <summary>
/// Tries to validate the model.
/// </summary>
/// <param name="model">The model.</param>
/// <returns>Whether the model is valid or not.</returns>
protected internal bool TryValidateModel(object model)
{
if (model == null)
{
throw new ArgumentNullException("model");
}
var metadataProvider = Configuration.Services.GetService<System.Web.Http.Metadata.ModelMetadataProvider>();
var validatorProviders = Configuration.Services.GetServices<System.Web.Http.Validation.ModelValidatorProvider>();
var metadata = metadataProvider.GetMetadataForType(() => model, model.GetType());
ModelState.Clear();
var modelValidators = metadata.GetValidators(validatorProviders);
foreach (var validationResult in modelValidators.SelectMany(v => v.Validate(metadata, null)))
{
ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
}
return ModelState.IsValid;
}
This uses the following simple extension methods to access the services :
/// <summary>
/// Services container extension methods.
/// </summary>
public static class ServicesContainerExtensions
{
/// <summary>
/// Gets the service.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <param name="services">The services.</param>
/// <returns>The service.</returns>
/// <exception cref="System.ArgumentNullException">services</exception>
public static TService GetService<TService>(this ServicesContainer services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
return (TService)((object)services.GetService(typeof(TService)));
}
/// <summary>
/// Gets the services.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <param name="services">The services.</param>
/// <returns>The services.</returns>
/// <exception cref="System.ArgumentNullException">services</exception>
public static IEnumerable<TService> GetServices<TService>(this ServicesContainer services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
return services.GetServices(typeof(TService)).Cast<TService>();
}
}
The advantage of using this method is that it reuses the MetadataProvider and ValidatorProvider(s) you have configured for your Web API application while the previous answer is retrieving the one configured in ASP.NET MVC.
ASP.NET MVC and WebAPI run through different pipelines.
Turns out TryValidateModel is not supported in WebAPI. There's a feature request over on CodePlex.
Unlike normal, I have code that actually works, but I'm wondering if it's the only (or best approach).
The basic Idea is I have an existing application that's handmade data layer is being ported to Entity Framework. As a compromise to minimize code changes, I'm working with existing methods, which tend to take a more disconnected approach. For example I have a lot of things like this:
UpdateNote(int noteId, string note)
I seem to have a method that works for this type of update without requiring a re-fetch:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note{ Id = noteId, Note = ""};
context.Notes.Attach(note);
note.Note = "Some Note";
context.SaveChanges();
It's a little ugly (though concise enough), so I would like to know if there is there a better approach to use with EF? Any downsides to this method, other than loosing built-in validation?
This is a pattern that will be used all over my app.
The following extension method for DbContext is an approach which would avoid to initialize your entities with some values different to the values you want to change it to.
public static class EFExtensions
{
public static void MarkAsModified(this DbContext context, object entity,
params string[] properties)
{
foreach (var property in properties)
context.Entry(entity).Property(property).IsModified = true;
}
}
You could then use it this way:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note { Id = noteId }; // only key properties required to set
note.Note = "Some Note";
note.SomeOtherProperty = 1234;
note.AndAnotherProperty = "XYZ";
context.Notes.Attach(note);
context.MarkAsModified(note, "Note", "SomeOtherProperty" , "AndAnotherProperty");
context.SaveChanges();
Note: This only works for scalar properties, not navigation properties.
Besides validation I could imagine that this approach is problematic for a proper concurrency checking.
Edit
According to #Adam Tuliper's comment below concurrency is likely not a problem because the concurrency check is skipped when an entity is attached manually to the context (without reading it from the database) and marked as modified to send an UPDATE command to the database. It just overwrites the lastest version in the DB. Thanks to Adam for pointing this out!
See the following code I use to easily attach a disconnected object back to the graph, assuming we're now going to save it.
public static class EntityFrameworkExtensions
{
/// <summary>
/// This class allows you to attach an entity.
/// For instance, a controller method Edit(Customer customer)
/// using ctx.AttachAsModified(customer);
/// ctx.SaveChanges();
/// allows you to easily reattach this item for udpating.
/// Credit goes to: http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx
/// </summary>
public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
/// <summary>
/// This marks an item for deletion, but does not currently mark child objects (relationships).
/// For those cases you must query the object, include the relationships, and then delete.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectSet"></param>
/// <param name="entity"></param>
public static void AttachAsDeleted<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}
public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
{
foreach (var item in entities)
{
objectSet.Attach(item);
objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
}
}
}