It appears that the RemoteAttribute validator introduced in ASP.NET MVC 3 does not validate on the server-side, only via JavaScript. If you turn off JS in your browser, you will find that on model binding, the validation controller action (that you specified when decorating a model property with the RemoteAttribute) will not be hit. In fact, if you inspect the source code for the RemoteAttribute, you will find the IsValid methods just returns true in all cases.
This seems to be quite an omission - I think most people would assume that the RemoteAttribute would work like all the other built-in validators and validate on both client-side and server-side. Instead, you must manually call your remote validation logic in your controller action.
Are people aware of this and has anyone tried to address it?
I have subclassed RemoteAttribute and overridden the IsValid method where I have access to RouteData, RouteName and Routes as well as a GetUrl method that returns the action URL. I was thinking about using reflection to call the action and get the result so I can see if it is valid or not, but are there any built-in methods that I can use without resorting to reflection?
Maybe its not the best code. If you have some recommendations please let me know.
Developed #MVC4
Model property with custom attribute
[CustomRemote("ValidateIP", "Validation", ErrorMessage = "Input is not a valid IP")]
Subclassed RemoteAttribute
/// <summary>
/// Custom Remote Attribute for Client an Server validation.
/// </summary>
public class CustomRemoteAttribute : RemoteAttribute
{
/// <summary>
/// List of all Controllers on MVC Application
/// </summary>
/// <returns></returns>
private static List<Type> GetControllerList()
{
return Assembly.GetCallingAssembly().GetTypes().Where(type => type.IsSubclassOf(typeof(Controller))).ToList();
}
/// <summary>
/// Constructor of base class.
/// </summary>
protected CustomRemoteAttribute()
{
}
/// <summary>
/// Constructor of base class.
/// </summary>
public CustomRemoteAttribute(string routeName)
: base(routeName)
{
}
/// <summary>
/// Constructor of base class.
/// </summary>
public CustomRemoteAttribute(string action, string controller)
: base(action, controller)
{
}
/// <summary>
/// Constructor of base class.
/// </summary>
public CustomRemoteAttribute(string action, string controller, string areaName)
: base(action, controller, areaName)
{
}
/// <summary>
/// Overridden IsValid function
/// </summary>
/// <param name="value"></param>
/// <param name="validationContext"></param>
/// <returns></returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// Find the controller passed in constructor
var controller = GetControllerList().FirstOrDefault(x => x.Name == string.Format("{0}Controller", this.RouteData["controller"]));
if (controller == null)
{
// Default behavior of IsValid when no controller is found.
return ValidationResult.Success;
}
// Find the Method passed in constructor
var mi = controller.GetMethod(this.RouteData["action"].ToString());
if (mi == null)
{
// Default behavior of IsValid when action not found
return ValidationResult.Success;
}
// Create instance of the controller to be able to call non static validation method
var instance = Activator.CreateInstance(controller);
// invoke the method on the controller with value
var result = (JsonResult)mi.Invoke(instance, new object[] { value });
// Return success or the error message string from CustomRemoteAttribute
return (bool) result.Data ? ValidationResult.Success : new ValidationResult(base.ErrorMessageString);
}
}
Validation Controller Code
/// <summary>
/// Controller for Client and Server validation
/// <remarks>disable OutputCache</remarks>
/// </summary>
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class ValidationController : Controller
{
/// <summary>
/// !!!!!!!!!!!!!!!!!! Needed for instance creation in custom attribute !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// </summary>
public ValidationController()
{
}
/// <summary>
/// IP regex pattern of my choice
/// </summary>
const string IpPattern = #"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
/// <summary>
/// MAC regex pattern of my choice
/// </summary>
const string MacPattern = "^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$";
/// <summary>
/// Validate IP
/// </summary>
/// <param name="ip">IP param is only submited on Serverside validation!!!</param>
/// <returns>Validation Result</returns>
public JsonResult ValidateIP(string ip)
{
// Check if ip and httpcontext is null to dodge NullReferenceException on Server side validation
if (string.IsNullOrEmpty(ip) && HttpContext == null)
{
return Json(false, JsonRequestBehavior.AllowGet);
}
/* Use IP on Serverside validation
* Use Querystring Param 0 to get IP from Client side vaildation without the need for the correct Id of input control */
string checkip = string.IsNullOrEmpty(ip) ? HttpContext.Request.QueryString[0] : ip;
if (string.IsNullOrEmpty(checkip))
{
return new JsonResult { Data = true, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
return new JsonResult
{
Data = Regex.IsMatch(checkip, IpPattern),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
This is the intended behavior of remote validation. There is no way to know what the implementation of IsValid would be so it simply returns true. If you want server side validation for the RemoteAttribute you should override IsValid just like you have done.
Remote validator often with an additional field. Here's the implementation for this case.
/// <summary>
/// Remote Attribute for Client an Server validation.
/// </summary>
public class RemoteWithServerSideAttribute : RemoteAttribute
{
/// <summary>
/// List of all Controllers on MVC Application
/// </summary>
/// <returns></returns>
private static IEnumerable<Type> GetControllerList()
{
return Assembly.GetCallingAssembly().GetTypes().Where( type => type.IsSubclassOf( typeof( Controller ) ) ).ToList();
}
/// <summary>
/// Constructor of base class.
/// </summary>
protected RemoteWithServerSideAttribute() {}
/// <summary>
/// Constructor of base class.
/// </summary>
public RemoteWithServerSideAttribute( string routeName ) : base( routeName ) {}
/// <summary>
/// Constructor of base class.
/// </summary>
public RemoteWithServerSideAttribute( string action, string controller ) : base( action, controller ){}
/// <summary>
/// Constructor of base class.
/// </summary>
public RemoteWithServerSideAttribute( string action, string controller, string areaName ) : base( action, controller, areaName ) {}
/// <summary>
/// Overridden IsValid function
/// </summary>
/// <param name="value"></param>
/// <param name="validationContext"></param>
/// <returns></returns>
protected override ValidationResult IsValid( object value, ValidationContext validationContext )
{
// Find the controller passed in constructor
var controller = GetControllerList().FirstOrDefault( x => x.Name == string.Format( "{0}Controller", this.RouteData["controller"] ) );
if ( controller == null )
{
// Default behavior of IsValid when no controller is found.
return ValidationResult.Success;
}
// Find the Method passed in constructor
var mi = controller.GetMethod( this.RouteData["action"].ToString() );
if ( mi == null )
{
// Default behavior of IsValid when action not found
return ValidationResult.Success;
}
// Create instance of the controller to be able to call non static validation method
var instance = Activator.CreateInstance( controller );
// invoke the method on the controller with value and "AdditionalFields"
JsonResult result;
if ( !string.IsNullOrWhiteSpace( AdditionalFields ) )
{
var additionalField = validationContext.ObjectType.GetProperty( AdditionalFields )
.GetValue( validationContext.ObjectInstance );
result = (JsonResult) mi.Invoke( instance, new [] { value, additionalField } );
}
else
result = (JsonResult)mi.Invoke( instance, new [] { value } );
// Return success or the error message string from CustomRemoteAttribute
string errorMessaqe = result.Data as string;
if (errorMessaqe == null)
{
bool isValid;
try
{
isValid = (bool) result.Data;
}
catch (Exception)
{
isValid = false;
}
return isValid ? ValidationResult.Success : new ValidationResult( base.ErrorMessageString );
}
else
return new ValidationResult( errorMessaqe );
}
}
As has been answered before, this is by design.
I have just stumbled upon a nice article on CodeProject - http://www.codeproject.com/Articles/361113/Extending-the-MVC-RemoteAttribute-to-validate-ser
The problem is that Remove Attribute requires Javascript validation, it does not work out of the box on the server-side... in other words, if you enable javascript validation, it will work and show the error(s), if you disable javascript and go to server validation, it will simply not show any errors on the modelstate, this is not a bug, its a feature hahaha... well, that's the way it was built (on ms)...
https://docs.devexpress.com/AspNet/17294/aspnet-mvc-extensions/data-editors-extensions/common-concepts/validation/remote-validation#requirements
If you need a remote validation attribute that works client and server side, you must create your own class inheriting RemoteAttribute, then you must deploy your own validation, check the following video: https://www.youtube.com/watch?v=qopOqppDwc4&t=110s
The video is almost correct, I did post a comment for controllers that use services via injection like dbcontext, etc... I did it and it works...
Related
I am implementing a system based on SignalR that will be pushing client events, for message serialization I would want to use MessagePack. When trying to implement the messaging I have run into a problem where SignalR fails to deserialize the messages on the client.
The messages are polymorphic and described with Union attributes, the standard MessagePack Serializer have no problem serializing and deserializing the messages BUT in case of Signal R it fails with error.
The error reported is System.InvalidOperationException: Invalid Union data was detected.
On the client serialization only works when using the actual class, if I try to use the interface or base class then the error appears.
Classes
[DataContract()]
[MessagePackObject()]
[Union(0,typeof(EntityChangeEventMessage))]
public abstract class EntityEventMessage : IEntityEventMessage
{
}
[DataContract()]
[MessagePackObject()]
public class EntityChangeEventMessage : EntityEventMessage
{
#region PROPERTIES
/// <summary>
/// Gets entity id.
/// </summary>
[DataMember(Order = 1)]
[Key(1)]
public int EntityId
{
get; set;
}
/// <summary>
/// Gets event type.
/// </summary>
/// <remarks>
/// This value identifies database operation such as create,delete,update etc.
/// </remarks>
[DataMember(Order = 2)]
[Key(2)]
public int EventType
{
get; set;
}
/// <summary>
/// Gets entity type name.
/// </summary>
[DataMember(Order = 3)]
[Key(3)]
public string EntityType
{
get; set;
}
#endregion
}
[Union(0,typeof(EntityChangeEventMessage))]
public interface IEntityEventMessage
{
}
So this works
connection.On("EntityEvent", (EntityChangeEventMessage d)
This dont work
connection.On("EntityEvent", (IEntityEventMessaged)
So in general it looks like the problem should be in the Microsoft.AspNetCore.SignalR.Protocols.MessagePack library ?
Anyone have implemented such functionality with success ?
Currently SignalR does not support polymorphism with MessagePack, more info here.
Can anyone tell Where can I get a JSON for Microsoft Sigin card for Teams bot and how to view it in a visualizer and can it be used to mask the password.
This is the OauthCard.cs file:
// <auto-generated>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
// </auto-generated>
namespace Microsoft.Bot.Schema
{
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// A card representing a request to perform a sign in via OAuth
/// </summary>
public partial class OAuthCard
{
/// <summary>
/// Initializes a new instance of the OAuthCard class.
/// </summary>
public OAuthCard()
{
CustomInit();
}
/// <summary>
/// Initializes a new instance of the OAuthCard class.
/// </summary>
/// <param name="text">Text for signin request</param>
/// <param name="connectionName">The name of the registered
/// connection</param>
/// <param name="buttons">Action to use to perform signin</param>
public OAuthCard(string text = default(string), string connectionName = default(string), IList<CardAction> buttons = default(IList<CardAction>))
{
Text = text;
ConnectionName = connectionName;
Buttons = buttons;
CustomInit();
}
/// <summary>
/// An initialization method that performs custom operations like setting defaults
/// </summary>
partial void CustomInit();
/// <summary>
/// Gets or sets text for signin request
/// </summary>
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
/// <summary>
/// Gets or sets the name of the registered connection
/// </summary>
[JsonProperty(PropertyName = "connectionName")]
public string ConnectionName { get; set; }
/// <summary>
/// Gets or sets action to use to perform signin
/// </summary>
[JsonProperty(PropertyName = "buttons")]
public IList<CardAction> Buttons { get; set; }
}
}
It can be found here. That being said, AFAIK, there is no visualizer for it, it's not in JSON, and there's nothing there to mask.
You can create a Sign In card in c#
SigninCard signincard = new SigninCard()
{
Text = "Click here to sign in",
Buttons = new List<CardAction>() {
new CardAction()
{
Title = "Authentication Required",
Type = ActionTypes.OpenUrl,
Value = $"{authenticationUrl}?{encodedCookie}"
}
}
};
Please give a try.
I have a try/catch set up like this.
First I try to log a record to our audit_log table.
try
{
AuditLog finalReportIssuedUpdate = new AuditLog
{
AuditId = auditId,
MilestoneId = 10,
LogText = $"Final Audit Report was issued (modified {DateTime.Now}).",
LogReason = reason,
LogDt = finalReportIssued,
StaffId = staffId,
//OfficeId
LogEntryDt = DateTime.Now
};
_auditLogRepo.Add(finalReportIssuedUpdate);
_auditLogRepo.SaveChanges();
}
Here I get a FK constraint that AuditID has to match an id in the Audit table.
Ok, so now it goes to the catch.
catch(Exception ex)
{
_errorLogRepo.Add(new ErrorLog
{
ErrorLogMessage = ex.Message,
ErrorLogStackTrace = ex.StackTrace,
ErrorLogStaffId = staffId,
ErrorLogDate = DateTime.Now
});
_errorLogRepo.SaveChanges();
}
But now when it goes to the errorLogRepo's SaveChanges() method, it is showing me the auditLogRepo's exception again about the FK constraint violation. It won't let me use EF to log the error:(
Here is AuditLogRepository:
public class AuditLogRepository : IAuditLogRepository
{
private ApplicationDbContext context;
/// <summary>
/// Provides an application database context to the class.
/// </summary>
/// <param name="ctx"></param>
public AuditLogRepository(ApplicationDbContext ctx)
{
context = ctx;
}
public IQueryable<AuditLog> AuditLogs => context.AuditLogs;
/// <summary>
/// Returns the AuditLog record matching the selected id.
/// </summary>
/// <param name="id">Id of the record to retrieve.</param>
/// <returns></returns>
public AuditLog Get(int id)
{
AuditLog auditLog = context.AuditLogs.Find(id);
return auditLog;
}
/// <summary>
/// Inserts a new audit log record into the database.
/// </summary>
/// <param name="auditLog"></param>
public void Add(AuditLog auditLog)
{
context.AuditLogs.Add(auditLog);
}
/// <summary>
/// Saves the changes to the AuditLog record
/// </summary>
public void SaveChanges()
{
context.SaveChanges();
}
}
Here is errorLogRepo:
public class ErrorLogRepo : IErrorLogRepo
{
private ApplicationDbContext context;
public ErrorLogRepo(ApplicationDbContext ctx)
{
context = ctx;
}
public IQueryable<ErrorLog> ErrorLogs => context.ErrorLogs;
public void Add(ErrorLog errorLog)
{
context.ErrorLogs.Add(errorLog);
}
public void SaveChanges()
{
context.SaveChanges();
}
}
Nothing special.
Two repos, two contexts. Why is this not letting me use a second repo in the catch right after an exception?
It seems both of your repositories rely on the same ApplicationDbContext instance.
So when you call the SaveChanges() method on either one of those, it actually calls the SaveChanges on the underlying context instance, which is shared, and results in an effort to insert both entities into the underlying storage. Hence the error.
There are two options you can go with from here:
1. In the catch block remove the added AuditLog record from the repo, before adding the error log.
2. Have two separate instances of ApplicaitonDbContext for two repository classes. This is heavy, so I would suggest option #1
I am trying to update an entity with a FK relationship in EntityFramework 4.3 Code First.
I try to attach to the related entites by calling: Entry(item).State = EntityState.Unchanged
I get the following exception: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I do not update these items nor have an id property for them on my main entity.
Is it possible to know which entities are attached or not ?
Thanks in advance,
Radu
You can find the answer here.
public bool Exists<T>(T entity) where T : class
{
return this.Set<T>().Local.Any(e => e == entity);
}
Place that code into your context or you can turn it into an extension like so.
public static bool Exists<TContext, TEntity>(this TContext context, TEntity entity)
where TContext : DbContext
where TEntity : class
{
return context.Set<TEntity>().Local.Any(e => e == entity);
}
You can use this method:
/// <summary>
/// Determines whether the specified entity key is attached.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="key">The key.</param>
/// <returns>
/// <c>true</c> if the specified context is attached; otherwise, <c>false</c>.
/// </returns>
internal static bool IsAttached(this ObjectContext context, EntityKey key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
ObjectStateEntry entry;
if (context.ObjectStateManager.TryGetObjectStateEntry(key, out entry))
{
return (entry.State != EntityState.Detached);
}
return false;
}
For example:
if (!_objectContext.IsAttached(entity.EntityKey))
{
_objectContext.Attach(entity);
}
If you have arrived here from an EF Core Lazy Loading scenario in which Navigation properties were filled in a data layer via DbSet<>.Include() clause(s) while the Entity was attached to a DbContext and then that Entity was detached and passed up to a business layer, consider adding something like this to your DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) method:
optionsBuilder.ConfigureWarnings(warn => warn.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning));
The error will be ignored and the values that were originally Include()d will be returned.
I use this extension method since I needed to check tracking based on values, not based on instance
internal static class DBExtensions
{
internal static bool IsAttached<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> condition) where TEntity : class
{
return dbSet.Local.Any(condition);
}
}
Usage:
if (!context.Items.IsAttached(y => y.ItemId == item.ItemId))
{
context.Items.Attach(item);
}
Is it true that it is possible to load View from anywhere without
implementation of custom VirtualPathProvider in MVC 3.0?
If it is true how to do it?
Basically it is not a problem to implement custom VirtualPathProvider which would load the
View from anywhere but my implementation working only in MVC 2.0 and not working wih MVC 3.0, fore some reason method GetFile newer called for not existed views in MVC 3.0 and in that case I am getting "Server Error in '/' Application."
I followed the same code for my custom VirtualPathProvider from here: http://buildstarted.com/2010/09/28/mvc-3-razor-view-engine-without-a-controller/
UPDATE 1
OK i did fix my problem with my custom VirtualPathProvider after i put registration of my custom VirtualPathProvider provider first line in the Application_Start()
protected void Application_Start()
{
//Should be first line before routes and areas registration.
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
When registration of custom VirtualPathProvider in Global.asax.cs folowing after AreaRegistration.RegisterAllAreas(); or RegisterRoutes(RouteTable.Routes); method method override VirtualFile GetFile(string virtualPath) wont work for "virtual Views".
UPDATE 2
does it means that the classes RazorView and RazorViewEngineRender is the answer?
UPDATE 3
If i have string representation of my razor view which does not exists in the file system (e.g i store razor views in database) how may i render it using this kind of approach http://buildstarted.com/2010/09/28/mvc-3-razor-view-engine-without-a-controller/
For example string representation of my View looks like this:
"#{
ViewBag.Title = ""About Us"";
}
<h2>About</h2>
<p>
Put content here.
</p>"
UPDATE 4
Now i see, to be able to use #Html.<something> custom TemplateBase should be implemented.
The sample of implementation of HtmlTemplateBase<T> could be fount here http://www.fidelitydesign.net/?p=239, but it won't work with RazorEngine v2, i am successfully getting template compiled, then after assembly loaded method public override void Execute() won't be executed i am getting an error: The method or operation is not implemented (stacktrace: http://tinypic.com/r/dcow4/7)
To make “public override T Model” happened i did change declaration of “public TModel Model” to “public virtual TModel Model” in “public abstract class TemplateBase : TemplateBase, ITemplate”. May be there is some another changes should be done? Or something in HtmlTemplateBase<T> should be done another way?
Don't be confused by Ben's (#BuildStarted) sample code in his article. He is detailing how to use an early version of the Razor ViewEngine to render templates without using a controller action. The intention was to be able to render templates in a generic fashion, rather than as specific page views. (This is what has evolved into our RazorEngine templating framework # http://razorengine.codeplex.com).
The VirtualPathProvider is still a core part of ASP.NET. There appears to be a general confusion about MVC 3's DependencyResolver being a replacement of a VirtualPathProvider but this is not the case, you still require a provider to be able to access content on a virtual path (which incidentally, all paths in ASP.NET are virtual).
Reverting my original answer, you should be able to achieve what you want purely through subclassing the RazorViewEngine and using that to create your views.
Have a look at this topic: http://coderjournal.com/2009/05/creating-your-first-mvc-viewengine/
No, loading a view from the database is not supported by default. You need to write your own VirtualPathProvider.
Note that Ben's blog post does not actually address directly the problem that you are trying to solve. The following blog post looks a lot closer to what you want: http://rebuildall.umbraworks.net/2009/11/17/ASP_NET_MVC_and_virtual_views. Note that it does not matter if you are trying to store razor or aspx views in the database. Virtual path providers in Asp.Net are simply about mapping a path to a stream of bytes that are the contents of the file represented by that path.
I ran into a similar issue implementing a VirtualPathProvider for embedded resource views. The solution was to implement GetFolder as well as GetFile. The view engine doesn't just call GetFile when you request that view. On the first request it looks through the views folder to find all available views. If that call doesn't include your database views in the list, they won't be found when you try to load them.
Everyone is correct. My post was not how to load Razor as a replacement but as a way to call razor without using MVC. Now...what you want is most likely related to my post here How to Download Razor View Engine Where I show how to create your own ViewEngine to host a razor page. It uses the same engine #Matthew Abbott and I use for the RazorEngine - which you can get from CodePlex. Unfortunately it's not complete but it should give you an idea on how to do it. (I'll post it here too)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Hosting;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace RazorViewEngine {
/// <summary>
/// ViewEngine for the RazorView. Provides basic file handling to load views.
/// </summary>
public class RazorViewEngine : IViewEngine {
string[] SearchLocations { get; set; }
Tuple<string, string, RazorView> Cache { get; set; }
VirtualPathProvider VirtualPathProvider { get; set; }
public RazorViewEngine() {
//{1} == Controller name
//{0} == View name
SearchLocations = new string[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
};
VirtualPathProvider = HostingEnvironment.VirtualPathProvider;
}
#region IViewEngine Members
public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) {
return CreateView(controllerContext, partialViewName, null, null, useCache);
}
public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
return CreateView(controllerContext, viewName, masterName, GetLayoutPath(controllerContext), useCache);
}
/// <summary>
/// Meat of the FindView methods.
/// </summary>
/// <param name="controllerContext">The current controller context for this request.</param>
/// <param name="viewName">The requested view name. </param>
/// <param name="masterName">The master page view name (currently unused)</param>
/// <param name="layoutPath">The layout path location (Replaces the masterpage in other view engines)</param>
/// <param name="useCache">Cache the viewpage?</param>
/// <remarks>The layout path is currently hardcoded to "Layout" and will look in the SearchLocations for that path</remarks>
/// <returns>Returns a ViewEngineResult with the requested view</returns>
public ViewEngineResult CreateView(ControllerContext controllerContext, string viewName, string masterName, string layoutPath, bool useCache) {
//grab the current controller from the route data
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
//for proper error handling we need to return a list of locations we attempted to search for the view
string[] SearchedLocations;
//get the actual path of the view - returns null if none is found
string viewPath = GetViewPath(viewName, controllerName, out SearchedLocations);
if (viewPath != null) {
RazorView view = new RazorView(this, controllerContext, viewPath, layoutPath);
return new ViewEngineResult(view, this);
}
//we couldn't find the view - return an array of all locations we've looked in
return new ViewEngineResult(SearchedLocations);
}
/// <summary>
/// Look for the view in the current file system
/// </summary>
/// <param name="viewName">The name of the View you're looking for</param>
/// <param name="controllerName">Current controller name</param>
/// <param name="SearchedLocations">out a list of locations searched</param>
/// <returns>A string value of the relative path</returns>
public string GetViewPath(string viewName, string controllerName, out string[] SearchedLocations) {
return FindPath(viewName, controllerName, out SearchedLocations);
}
/// <summary>
/// Look for the view in the current file system
/// </summary>
/// <param name="viewName">The name of the View you're looking for</param>
/// <param name="controllerName">Current controller name</param>
/// <param name="SearchedLocations">out a list of locations searched</param>
/// <returns>A string value of the relative path</returns>
public string FindPath(string viewName, string controllerName, out string[] SearchedLocations) {
SearchedLocations = new string[SearchLocations.Length];
for (int i = 0; i < SearchLocations.Length; i++) {
string virtualPath = string.Format(SearchLocations[i], viewName, controllerName);
SearchedLocations[i] = virtualPath;
//check the active VirtualPathProvider if the file exists
if (VirtualPathProvider.FileExists(virtualPath)) {
//add it to cache - not currently implemented
return VirtualPathProvider.GetFile(virtualPath).VirtualPath;
}
}
return null;
}
/// <summary>
/// Get the layout virtual path
/// </summary>
/// <param name="controllerContext">The current Controller context for this request</param>
/// <returns>A string virtual path</returns>
public string GetLayoutPath(ControllerContext controllerContext) {
//This should probably be added to a list of locations - I'm not sure exactly
//what I need to do with this yet.
string[] locations;
return FindPath("Layout", controllerContext.RouteData.GetRequiredString("controller"), out locations);
}
/// <summary>
/// Current irrelevant
/// </summary>
/// <param name="controllerContext">The active controller context</param>
/// <param name="view">View to release</param>
public void ReleaseView(ControllerContext controllerContext, IView view) {
IDisposable disposable = view as IDisposable;
if (disposable != null) {
disposable.Dispose();
}
}
#endregion
}
/// <summary>
/// Implements IView and renders a Razor
/// </summary>
public class RazorView : IView {
ControllerContext ControllerContext;
string ViewPath;
string LayoutPath;
RazorViewEngine Engine;
public RazorView(RazorViewEngine engine, ControllerContext controllerContext, string viewPath, string layoutPath) {
//load the file
this.ControllerContext = controllerContext;
this.ViewPath = viewPath;
this.LayoutPath = layoutPath;
this.Engine = engine;
}
#region IView Members
/// <summary>
/// Converts Razor to html and writes it to the passed in writer
/// </summary>
/// <param name="viewContext"></param>
/// <param name="writer"></param>
public void Render(ViewContext viewContext, System.IO.TextWriter writer) {
//View contents
string contents = new StreamReader(VirtualPathProvider.OpenFile(ViewPath)).ReadToEnd();
string layoutContents = LayoutPath == null
? null
: new StreamReader(VirtualPathProvider.OpenFile(LayoutPath)).ReadToEnd();
contents = Parse(contents);
string output;
output = contents;
writer.Write(output);
}
/// <summary>
/// Converts Razor to html
/// </summary>
/// <param name="Razor">Razor text</param>
/// <returns>Html formatted Razor text</returns>
string Parse(string Razor) {
//Where do I get the model From
return RazorEngine.Razor.Parse(Razor);
}
#endregion
}
}