I'm using the Razor/Markdown engine from ServiceStack and am having a bit of difficulty applying my own custom Template/Layout to some rendered Markdown. The Markdown content renders perfectly, I just want to inject that into a Template/Layout file of my choice.
Currently I have this (which works perfectly) :
var rootPath = HttpContext.Current.Server.MapPath("~/");
var markdownPath = Path.Combile(rootPath, "NotFound.md");
var format = new MarkdownFormat();
var markdownContent = File.ReadAllText(markdownPath);
const string pageTitle = "Not Found";
var page = new MarkdownPage(format, rootPath, pageTitle, markdownContent);
format.AddPage(page);
var scopeArgs = new Dictionary<string, object>();
var html = format.RenderDynamicPageHtml(pageTitle, scopeArgs);
Now I have a layout file I wish to use located at "~/ErrorLayout.cshtml", however I have no idea how to inject it. At first I thought to set the Template variable on MarkdownPage to my Layout file's path, however that didn't work. I then tried to call format.AddLayout() and unfortunately that threw an exception.
Any help would be highly appreciated, feel free to ask for any further clarification from myself if I have not made what I am trying to do clear.
So I have solved the problem, however I am uncertain if what I have done is the correct method or not, however it works and no exceptions are thrown. Perhaps somebody with a bit more knowledge could correct me if I have done this incorrectly (so I will leave this question open for a couple days before accepting my answer).
I have created a new class which implements the IVirtualPathProvider interface, and named it PathProvider
I have also created a class which implements the IVirtualFile interface, and named it VirtualFile
I then set the VirtualPathProvider in my instance of MarkdownFormat to a new instance of PathProvider. I then set the Template variable on my instance of Markdownpage to a relative path to the cshtml layout/template file I want to make use of, and within the two classes I mentioned before, returned related content for this Template when requested to do so.
My code now looks like this (in case somebody else has the same problem as me) :
var rootPath = HttpContext.Current.Server.MapPath("~/");
if (contents == null)
{
var notFoundPath = Path.Combine(rootPath, "NotFound.md");
contents = File.ReadAllText(notFoundPath);
}
var format = new MarkdownFormat
{
VirtualPathProvider = new PathProvider()
};
const string pageTitle = "Not Found";
var page = new MarkdownPage(format, rootPath, pageTitle, contents)
{
Template = "~/_Layout.cshtml"
};
format.AddPage(page);
var view = new Dictionary<string, object>();
var html = format.RenderDynamicPageHtml(pageTitle, view);
My PathProvider class looks like this :
public class PathProvider : IVirtualPathProvider
{
public IVirtualDirectory RootDirectory { get; private set; }
public string VirtualPathSeparator { get; private set; }
public string RealPathSeparator { get; private set; }
public string CombineVirtualPath(string basePath, string relativePath)
{
throw new NotImplementedException();
}
public bool FileExists(string virtualPath)
{
throw new NotImplementedException();
}
public bool DirectoryExists(string virtualPath)
{
throw new NotImplementedException();
}
public IVirtualFile GetFile(string virtualPath)
{
return new VirtualFile(this, virtualPath);
}
public string GetFileHash(string virtualPath)
{
throw new NotImplementedException();
}
public string GetFileHash(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
public IVirtualDirectory GetDirectory(string virtualPath)
{
throw new NotImplementedException();
}
public IEnumerable<IVirtualFile> GetAllMatchingFiles(string globPattern, int maxDepth = 2147483647)
{
throw new NotImplementedException();
}
public bool IsSharedFile(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
public bool IsViewFile(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
}
and finally my VirtualFile class :
public class VirtualFile : IVirtualFile
{
public IVirtualDirectory Directory { get; private set; }
public string Name { get; private set; }
public string VirtualPath { get; private set; }
public string RealPath { get; private set; }
public bool IsDirectory { get; private set; }
public DateTime LastModified { get; private set; }
public IVirtualPathProvider VirtualPathProvider { get; private set; }
public string Extension { get; private set; }
public VirtualFile(IVirtualPathProvider virtualPathProvider, string filePath)
{
VirtualPathProvider = virtualPathProvider;
VirtualPath = filePath;
RealPath = HttpContext.Current.Server.MapPath(filePath);
}
public string GetFileHash()
{
throw new NotImplementedException();
}
public Stream OpenRead()
{
throw new NotImplementedException();
}
public StreamReader OpenText()
{
throw new NotImplementedException();
}
public string ReadAllText()
{
return File.ReadAllText(RealPath);
}
}
Related
So I want to write an interface, which should be able to be implemented with any data. This is interface i wrote till now. The reason I chose IEnumerable is because I need to give class Computer or struct Processor
public interface IData<T> where T : IEnumerable<object>
{
public T ReadData();
public void WriteData(T list);
}
And I have two different datas, one is Computer, which is a class. And the other one is Processor (struct)
public struct Processor
{
public string Name { get; set; }
public string AmazonLink { get; set; }
public string AmazonBin { get; set; }
public Processor(string name, string link)
{
Name = name;
try
{
//constructor parses elements which is needed to generate AmazonURL in URLGenerator project
AmazonLink = link.Substring(0, link.IndexOf("&dc"));
string binStart = link.Substring(link.IndexOf("bin%") + 4);
AmazonBin = "%7C" + binStart.Substring(2);
}
catch (Exception e)
{
throw new InnerCustomException("Erorr occured while trying to substring the link", e);
}
}
I tried to do that like this, but it seems like I am not allowed to do that because of boxing?
public class ProcessorServiceCSV : IData<IEnumerable<Processor>>
{ private string Path { get; set; }
private FileMode Filemode { get; set; }
public ProcessorServiceCSV(string path, FileMode fileMode)
{
Path = path;
Filemode = fileMode;
}
//reads Processor list from CSV file
public IEnumerable<Processor> ReadData()
{
try
{
using (var reader = new StreamReader(Path))
using (var csv = new CsvReader(reader))
{
csv.Configuration.CultureInfo = CultureInfo.InvariantCulture;
csv.Configuration.Delimiter = ",";
csv.Configuration.RegisterClassMap<ProcessorMap>();
var records = csv.GetRecords<Processor>().ToList();
return records.ToList();
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
} public void WriteData(IEnumerable<Processor> processors)
{
try
{
using (var stream = File.Open(Path, Filemode))
using (StreamWriter sw = new StreamWriter(stream))
using (CsvWriter cw = new CsvWriter(sw))
{
foreach (Processor processor in processors)
{
cw.Configuration.RegisterClassMap<ProcessorMap>();
cw.WriteRecord<Processor>(processor);
cw.NextRecord();
}
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (FileLoadException)
{
throw new DataCustomException("File could not be opened", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
}
}
}
I know I could change Processor from struct to class, but is it possible to keep struct? Thank you in advance
You have a lot of other problems, including not giving us a complete, working bit of code.
However, it looks like you should be able to do what you want to do if you use an Interface for the Processor struct instead of the actual struct type.
Also, notice how I changed the type for T in your classes. You don't need IEnumerable in your T constraint. I did delete some of your code to get it to somewhat work (the exception in the struct constructor, e.g.), so you will need to do some more work here.
public interface IData<T>
{
IEnumerable<T> ReadData();
void WriteData(IEnumerable<T> list);
}
public interface IProcessor {
string Name { get; set; }
string AmazonLink { get; set; }
string AmazonBin { get; set; }
}
public struct Processor : IProcessor
{
public string Name { get; set; }
public string AmazonLink { get; set; }
public string AmazonBin { get; set; }
public Processor(string name, string link)
{
Name = name;
//constructor parses elements which is needed to generate AmazonURL in URLGenerator project
AmazonLink = link.Substring(0, link.IndexOf("&dc"));
string binStart = link.Substring(link.IndexOf("bin%") + 4);
AmazonBin = "%7C" + binStart.Substring(2);
}
}
public class ProcessorServiceCSV<T> : IData<T> where T: IProcessor
{ private string Path { get; set; }
private FileMode Filemode { get; set; }
public ProcessorServiceCSV(string path, FileMode fileMode)
{
Path = path;
Filemode = fileMode;
}
//reads Processor list from CSV file
public IEnumerable<T> ReadData()
{
try
{
using (var reader = new StreamReader(Path))
using (var csv = new CsvReader(reader))
{
csv.Configuration.CultureInfo = CultureInfo.InvariantCulture;
csv.Configuration.Delimiter = ",";
csv.Configuration.RegisterClassMap<ProcessorMap>();
var records = csv.GetRecords<Processor>().ToList();
return records.ToList();
}
}
catch (FileNotFoundException)
{
throw new DataCustomException("File not found", this);
}
catch (Exception e)
{
throw new DataCustomException("Something's wrong happened:" + e.Message, this);
}
}
}
Is this the basic skeleton code of what you are trying to do? Note that the generic collection is IEnumerable<T> and not IEnumetable<object> and hence I updated your IData<T> definition
public class Computer
{
}
public struct Processor
{
}
public interface IData<T>
{
IEnumerable<T> ReadData();
void WriteData(IEnumerable<T> list);
}
public class ComputerData : IData<Computer>
{
public IEnumerable<Computer> ReadData()
{
throw new NotImplementedException();
}
public void WriteData(IEnumerable<Computer> list)
{
throw new NotImplementedException();
}
}
public class ProcessorData : IData<Processor>
{
public IEnumerable<Processor> ReadData()
{
throw new NotImplementedException();
}
public void WriteData(IEnumerable<Processor> list)
{
throw new NotImplementedException();
}
}
Please indicate if this code meets your requirements, and if not why.
Response is successful, i can view it in Visual Studio, but when i try to get returned data, its null.
This is API https://yoda-api.appspot.com/api/v1/yodish?text=I%20am%20yoda
And this is my code:
public class YodishModel
{
public string yodish { get; set; }
}
public class YodishResult
{
public YodishModel Result { get; set; }
}
public class YodishService : iService
{
public string GetText(string text)
{
Lazy<RestClient> client = new Lazy<RestClient>(() => new RestClient($"http://yoda-api.appspot.com/api/v1/yodish?text={text}"));
var request = new RestRequest();
var response = client.Value.Execute<YodishResult>(request);
if (response.IsSuccessful)
{
return response.Data.Result.yodish;
}
return null;
}
public string ToUrl(string text)
{
return HttpUtility.UrlEncode(text);
}
}
Response is successful, i can view the result, but Result is null (NullPointerException).
Also, is there a way to use parameters here instead of using string interpolation? 'text' is part of the URL which is officially not a paremeter.
In your case, you were deserializing using a mismatched object. This is what I did to fix it:
public class YodishModel
{
public string yodish { get; set; }
}
public class YodishService
{
public string GetText(string text)
{
Lazy<RestClient> client = new Lazy<RestClient>(() => new RestClient($"https://yoda-api.appspot.com/api/v1/"));
var request = new RestRequest($"yodish").AddQueryParameter("text", Uri.EscapeDataString(text), true);
var response = client.Value.Execute<YodishModel>(request);
if (response.IsSuccessful)
{
return Uri.UnescapeDataString(response.Data.yodish);
}
return null;
}
}
I also added the AddQueryParameter, as you mentioned.
Let's assume I have a simple class
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string Image { get; set; } // Base64 coded Bitmap object
}
The real world object is way more complex. I use XmlSerializer.Serialize to save instances to a file.
The content from image is generated this way:
byte[] result = null;
using (var image = Bitmap.FromFile(#"filename"))
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
result = stream.ToArray();
}
var content = Convert.ToBase64String(result);
Now I have a breaking change.
In the future I want to save the raw image Data (also as base64) without converting it to jpg.
So my New object will look like this:
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
}
Luckily I already store a version attribute (currently 1 for every xml file). For new Items I can
Now I am wondering if there are any best practices on how to deal with model changed.
I was thinking about this approach:
Still define a property ImageString in my class, marked as obsolete.
the property will only have a setter but no getter
If ImageString is set I just update RawImageString
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
[Obsolete("Use RawImageString instead")]
public string ImageString
{
set
{
this.RawImageString = value;
this.Version = 2;
}
}
}
This should work well, but it would require me to maintain the legacy property until forever. I would prefer
// depending on the version property XmlSerializer should return a
// different Document implementation
var serializer = new XmlSerializer(typeof(IDocument));
var document = (IDocument)serializer.Deserialize(reader);
Of course I could achive this with a factory method, but that would require two reads. One for the version the second for the concrete result.
Eventually I solved it this way.
A Document is created using a static method anyway.
Now I check if version matches the current version and start a migration if not.
public const int CURRENT_VERSION = 2;
public static DocumentOpen(string path)
{
var controller = new DocumentController();
var item = controller.ReadXml(path);
if (item.Version != CURRENT_VERSION)
{
var migrator = new DocumentMigrator(item, path);
migrator.MigrateToLatestVersion();
}
return item;
}
The migrator looks like this
public class DocumentMigrator
{
private Document item;
private String path;
public DocumentMigrator(Documentitem, string path)
{
this.item = item;
this.path = path;
}
public void MigrateToLatestVersion()
{
Migrate(Document.CURRENT_VERSION);
}
public void Migrate(int to)
{
Migrate(item.Version, to);
}
private void Migrate(int from, int to)
{
if (from < to)
{
while (item.Version < to)
Up(item.Version + 1);
}
else if (from > to)
{
while (item.Version < to)
Down(item.Version - 1);
}
}
private void Down(int version)
{
throw new NotImplementedException();
}
private void Up(int version)
{
if (version == 2)
{
var stream = File.OpenRead(path);
var serializer = new XmlSerializer(typeof(DocumentV1));
var document = (DocumentV1)serializer.Deserialize(stream);
this.item.RawImageString = document.ImageString;
}
else
{
throw new NotImplementedException();
}
this.item.Version = version;
}
}
public class DocumentV1
{
public string ImageString { get; set; }
}
The idea is that I created a helper class DocumentV1 which only contains the properties that I want to migrate. This could even be avoided by using dynamics or XElement.
I perform the upgrade and update the version from the original class. A Backward migration could also be implemented in the Down method but that is not required at the moment.
I'm trying to save my Photo class that has a byte[] File field. When trying to save it using context is throws the error
Object reference not set to an instance of an object.
But when debugging I can see that it is not null. I can see all of the properties of the the class including the values in the byte array.
public class PhotoRepository
{
private static BlogContext _ctx;
public PhotoRepository()
{
_ctx = new BlogContext();
}
public static void Save(Photo p)
{
_ctx.Photos.Add(p);
_ctx.SaveChanges();
}
}
controller
public class PhotoController : Controller
{
public ActionResult Index()
{
using (var ctx = new BlogContext())
{
return View(ctx.Photos.AsEnumerable());
}
}
public ActionResult Upload()
{
return View(new Photo());
}
[HttpPost]
public ActionResult Upload(PhotoViewModel model)
{
var photo = new Photo();//Mapper.Map<PhotoViewModel, Photo>(model);
if (ModelState.IsValid)
{
photo.AlternateText = model.AlternateText;
photo.Description = model.Description;
photo.File = MapStreamToFile(model.File);
photo.Name = model.Name;
PhotoRepository.Save(photo);
return RedirectToAction("Index");
}
return View(photo);
}
public byte[] MapStreamToFile(HttpPostedFileBase file)
{
using (var stream = file.InputStream)
{
var memoryStream = stream as MemoryStream;
if (memoryStream == null)
{
memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
}
return memoryStream.ToArray();
}
}
}
Photo
public class Photo
{
public int Id { get; set; }
public Byte[] File { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string AlternateText { get; set; }
}
PhotoViewModel
public class PhotoViewModel
{
public int Id { get; set; }
public HttpPostedFileBase File { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string AlternateText { get; set; }
}
I think the problem is that _ctx is null. Notice that you declare it and Save static, but _ctx is only instantiates in public PhotoRepository(), which is the constructor. As long as it should really be static, instantiate it statically instead of in the constructor:
public static class PhotoRepository
{
private static BlogContext _ctx = new BlogContext();
public static void Save(Photo p)
{
_ctx.Photos.Add(p);
_ctx.SaveChanges();
}
}
I also changed the class to static, since I only see it contains static members. This may not be correct if you intend more for this class.
Edit: (thanks #pst) I see from looking at your code more, I think this might really be a better design:
public class PhotoRepository : IDisposable
{
private BlogContext _ctx = new BlogContext();
public void Save(Photo p)
{
_ctx.Photos.Add(p);
_ctx.SaveChanges();
}
void IDisposable.Dispose() { _ctx.Dispose(); }
}
And then always be sure to dispose PhotoRepository when you're done with it. The reason for my suggested change here is that BlogContext is disposable, and is used with using in another place.
I have the following abstract class:
public abstract class TemplateBase
{
public abstract string TemplateName { get; }
public string RuntimeTypeName { get { return GetType().FullName; } }
public abstract List<AreaContainer> TemplateAreas { get; }
}
then these 2 inherited classes:
public class SingleColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Single column"; } }
public AreaContainer CenterColumn { get; private set; }
public SingleColumnTemplate()
{
this.CenterColumn = new AreaContainer("Middle");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.CenterColumn };
}
return this.templateAreas;
}
}
}
and
public class TwoColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Two column"; } }
public AreaContainer LeftColumn { get; private set; }
public AreaContainer RightColumn { get; private set; }
public TwoColumnTemplate()
{
LeftColumn = new AreaContainer("Left");
RightColumn = new AreaContainer("Right");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.LeftColumn, this.RightColumn };
}
return this.templateAreas;
}
}
}
I also have this class that is my model for editing:
public class ContentPage
{
public virtual int ContentPageId { get; set; }
public virtual string Title { get; set; }
public TemplateBase Template { get; set; }
}
Question:
for my ActionResults I have the following:
[HttpGet]
public ActionResult Edit()
{
var row = new ContentPage();
var template = new TwoColumnTemplate();
// Areas
HtmlArea html_left = new HtmlArea();
html_left.HtmlContent = "left area html content";
HtmlArea html_right = new HtmlArea();
html_right.HtmlContent = "right area html content";
template.LeftColumn.Areas.Add(html_left);
template.RightColumn.Areas.Add(html_right);
row.Template = template;
return View(row);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ContentPage row)
{
// Here i could loop through List -TemplateAreas and save each template Area to Db. I guess that would work
return this.View(row);
}
Question:
For HttpGet- how would I load row Template from the database? since it could be SingleColumnClass or TwoColumnClass.
how would my ViewModel look like to solve this?
thanks
You can write your own Model Binder that is responsible for binding TemplateBase. You will still need to have a way of knowing (in the model binder) which type you will be using a runtime, but you can always delegate that to a factory or service locator of some sort. I did a quick google search and here is a blog post I found that gives you some information for making a model binder for a similar scenario:
http://weblogs.asp.net/bhaskarghosh/archive/2009/07/08/7143564.aspx
EDIT: The blog leaves out how you tell MVC about your model binder. When the application starts, you can add your model binder to System.Web.Mvc.ModelBinders.Binders
HTH
You need to know the template type in you controller, so you can pass a parameter from the view to the controller, indicating the type (SingleColumn or TwoColumn). You could do this witn a Enum:
public enum TemplateType
{
SingleColumn,
TwoColumn
}
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template;
if (templateType == TemplateType.SingleColumn)
{
template = new SingleColumnTemplate();
}
else
{
template = new TwoColumnTemplate();
}
...
return View(row);
}
When you create the action link from your view you can specify:
<%= Html.ActionLink("Edit",
"Edit",
"YouController",
new
{
// singlecolumn or twocolumn
// depending on your concrete view
TemplateType = TemplateType.xxx
},
null);
I wonder if you could do something like this?
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template = (TemplateBase)Activator.CreateInstance(templateType);
...
return View(row);
}
templateType would have to be the exact name of your inherited classes (you can ignore case)