Adding Custom attributes to the page directive in asp.net-mvc - asp.net-mvc

I have created a base class that derives from ViewPage with a custom public attribute that I want to be able to set in the #page directive however I am getting the following error.
Error 24 Error parsing attribute 'attrame': Type 'System.Web.Mvc.ViewPage' does not have a public property named 'attrname'.
the directive looks as so
<%# Page Title="" Language="C#" AttrName="Test" Inherits="Web.Helpers.Views.BaseViewPage" %>
The problem seems to be that it doesn't recognize the base class from the Inherits tag. I thought this should work from reading around on the internet. Has anyone else tried this or have any idea why this isn't working?

This is unfortunately not supported in ASP.NET MVC 1.0 or ASP.NET MVC 2.
The reason for this is an implementation detail of some MVC-specific parser logic that is used in ASPX/ASCX/MASTER files. If the view page's (or view master page's or view user control's) base type is a generic type there is logic that hard-codes the base class for the ASP.NET parser's sake to be just regular ViewPage (or ViewMasterPage or ViewUserControl).
Because the ASP.NET parser looks at the base class that MVC tells is, it will only ever be ViewPage, and thus it doesn't recognize the new property that you added, and thus it reports an error.
It is worth mentioning that this applies only if the base class you specify in the view page is generic. If you use a non-generic type then it should work just fine and you should be able to set values on custom properties.
I can think of two workarounds:
1) Create custom page base types for every type you need. This solution is rather easy, though cumbersome:
public class MyBasePage<TModel> : ViewPage<TModel> {
...
}
public class CustomerPage : MyBasePage<Customer> { }
public class ProductPage : MyBasePage<Product> { }
And then use only the non-generic derived types in the view pages' "inherits" attribute.
2) Copy the MVC source code (see links below) from the ViewTypeParserFilter into your project and make some small changes. The key method to change is the PreprocessDirective() method. Towards the bottom is an if() statement that overrides the "inherits" attribute to be one of a few hard-coded values. As you'll see, this code runs only if the declared base type is generic (hence my earlier comment).
It's up to you to decide exactly how you want to change this code. The key thing here is that the type name must be a type that .NET's Type.GetType() method can understand. That is, you have to use the CLR syntax for constructs such as generics and not the C# or VB syntax. For example, while in C# you can say:
System.Web.Mvc.ViewPage<Customer>
In the CLR syntax it's something like:
System.Web.Mvc.ViewPage`1[MyApp.Models.Customer]
Hopefully one of the two options above suits you.
Source code links:
ASP.NET MVC 1.0 source code download
ASP.NET MVC 2 RC source code

You need to set the base type in the web.config to get around this. Go to
/Views/Web.config
and change the
configuration/system.web/pages
and change the attribute pageBaseType to your class.
<pages pageBaseType="Web.Helpers.Views.BaseViewPage" {your other attributes here} />

Related

Use VB.Net module inside MVC 5 Razor page

I have a module written in VB.Net that has several read-only properties. It looks like this:
Module Name1
Public ReadOnly Property ClientID As String
Get
Return "[some data here]"
End Get
End Property
Public ReadOnly Property ClientKey As String
Get
Return "[some data here]"
End Get
End Property
End Module
Nothing real fancy here. What I'm trying to do is use this module in an MVC 5 Razor page like this:
<span>#Name1.ClientID - #Name1.ClientKey</span>
Again, nothing fancy. But running the code produces the following error:
Name1 is not accessible in this context because it is 'Friend'.
Modules have friend access by default, which prevents them from being accessible outside of the current assembly. This is what I want. But it doesn't work when called from a Razor page. I've check to ensure that the namespaces were imported and such but no go. (Changing the module's access to public gets things working but I don't wish to make it public.)
What am I doing wrong?
I found a solution to my problem here: ASP.NET MVC: How to set global ViewBag properties and have them available in each View
By creating a custom ActionFilter, I was able to set ViewBag properties and set their values to the properties in my module. Then I added that filter to the GlobalFilters collection. From there, I can access the ViewBag properties easily in Razor pages.

What is the VB equivalent of #model dynamic in C# Razor?

I have a View (vbhtml) in ASP.NET MVC 5 which uses a dynamic model, I know it's easy in C# to do this by writing
#model dynamic
But how can I specify this in vbhtml?
There is no equivalent of C# Dynamic in vb.net instead you can replace dynamic into Object and make sure you set option strict off.
The dynamic keyword brings Option Strict Off equivalent functionality to C#.
The key difference based on MSDN is:
If a late-bound call is made to an object that implements the
IDynamicMetaObjectProvider interface, Visual Basic binds to the
dynamic object by using that interface. If a late-bound call is made
to an object that does not implement the IDynamicMetaObjectProvider
interface, or if the call to the IDynamicMetaObjectProvider interface
fails, Visual Basic binds to the object by using the late-binding
capabilities of the Visual Basic runtime.
And since you mentioned:
The problem is with late binding not option strict
You reference a dynamic object by using late binding. In C#, you specify the type of a late-bound object as dynamic. In Visual Basic, you specify the type of a late-bound object as Object. For more information, see dynamic (C# Reference) and Early and Late Binding (Visual Basic).
You can create custom dynamic objects by using the classes in the System.Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class. You can then override the members of the DynamicObject class to provide run-time dynamic functionality.
An example could be found in MSDN.
Update:
VB binder does not work with things typed as dynamic in medium trust. Try setting your app to full trust. Also The Option Strict On disallows late binding In VB.Net. If you are trying to use strongly typed helpers like Html.EditorFor while your view is not strongly typed to a class, So you need to indicate the model type in the #Page definition:
<%# Page
Language="VB"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage(Of YourApplication.YourModelClass)" %>
Then you can safely use those helper methods.
VB equivalent of #model dynamic in C# Razor is #ModelType

ASP.Net MVC Action methods compile time errors in Razor cshtml files

e Is there any way where one can produce compile time errors if an action method is not available in the controller through using the Html.Action?
For example, let us say we have a controller named LayoutMainPartialViewsController and within such a controller, we have a method Header which outputs the Header Partial View located at /Views/Shared/PartialViews/_Header.cshtml
public class LayoutMainPartialViewsController : Controller
{
//
// GET: /LayoutMainPartialViews/
public ActionResult Header()
{
return PartialView("/Views/Shared/PartialViews/_Header.cshtml");
}
}
Then to include such partial view, we would call within Razor
#Html.Action("Header", "LayoutMainPartialViews");
If for some reason, the method named Header is renamed to Header2, no compile time errors are given but only run time errors.
I would like to be able to have compile time errors to minimize as much as possible runtime errors as they are much more difficult to identify.
On another note, would it make sense to call something like?
#(new LayoutMainPartialViewsController().Header())
The method above returns an ActionResult which needs to be converted as String the same way Html.Action does.
This way, if we change Header to Header2 and have <MvcBuildViews>true</MvcBuildViews> in the csproj, an error is provided compile time.
T4MVC is a T4 template for ASP.NET MVC apps that creates strongly typed helpers that eliminate the use of literal strings in many places.
This does exactly what I require and removes the usage of 'magic strings' as opposed to strongly typed identifiers. Compile time errors are provided even in views if the <MvcBuildViews>true</MvcBuildViews> is set to true.
Thanks #StuartLC for the tip!

How to implement a custom view engine in ASP.Net MVC that (sorta) isn't file-based?

I've created a fairly generic view engine that I created initially without aiming toward ASP.Net MVC. Now though, I think it would be a good idea to have it to where it can at least be easily used by MVC projects. I'm wondering if my project would map well to the ASP.Net MVC style though.
The problem I'm having is that my view engine generates everything at compile-time via T4 templates. This means that everything is statically typed for the most part. Most of MVC seems to be a bit loosely typed however.
So for some view you might have this code generated:
class MyView{
public string Foo{get;set;}
public int Bar{get;set;}
public string Render(){
return "This is my view: "+Foo+(string)Bar;
}
}
And because of how it works, even if there is a views/FooView.html file, it may get processed into a class named MyView.
So how exactly is the best way to assign ViewData to say Foo and Bar of MyView? Should I just impose the limitation that you can only use a single field in the views(basically being a ViewData) or?
The other major problem I see is that MVC is almost completely file based. When you say RenderView("MyView",data); it will look in /views/ for a file named MyView.aspx or whatever(you can change where it looks and the file extension of course). The problem is that MyView could have been compiled from a file named FooView.html. Should I basically just generate a huge list for every view available with their mappings from class name to filesystem name? Or is there a better way?
Note: Because I generate all the views(and possibly could generate the MVC view engine) from a T4 template, this means I could write huge lists and other extremely tedious or bad code. But I feel like there is a better way than a huge list in this case, and that there will be underlying problems with only keeping a list.
You may extend my T4 based view engine to create your class file from your model, compile it in memory and cache it, and pass your view model to the compiled assembly via reflection.
http://mvct4viewengine.codeplex.com/

Issues During ASP.NET MVC Upgrade from Preview 5 to Beta?

What issues or refactoring did you have to do when you upgraded from ASP.NET MVC Preview 5 to the newly released Beta version?
Issue number one: Yellow screen of death.
CS0234: The type or namespace name 'Mvc' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)
Solution: I removed all references in my project and re-added them, pointing to the assemblies in program files\asp.net\asp.net mvc beta\assemblies, but that didn't solve the problem.
I had a system.web.mvc dll in the gac (no idea how). Tried to delete it. Unable to; assembly is required by one or more applications. Had to find the assembly as described here and delete the registry entry. I was then able to remove the gac's version of system.web.mvc.
This STILL didn't fix the problem. I had to RE-ADD the references AGAIN. Now its working.
Just to be clear!!! The beta assemblies were dropped under Program Files, while an older version of System.Web.Mvc was in the GAC.
I'm about to do this myself. Here's the list of changes from the readme:
Changes Made Between CodePlex Preview 5 and Beta
Changed the default validation messages to be more end-user friendly.
Renamed CompositeViewEngine to AutoViewEngine.
Added a Url property to Controller of type UrlHelper. This makes it convenient to generate routing-based URLs from within a controller.
Added the ActionNameSelectorAttribute abstract base class, which serves as the base type for ActionNameAttribute. By inheriting from this base attribute class, you can create custom attributes that participate in action selection by name.
Added a new ReleaseView method to IViewEngine that allows custom view engines to be notified when a view is done rendering. This is useful for cleanup or for view-pooling scenarios.
Renamed the ControllerBuilder method DisposeController to ReleaseController to fit with the pattern that is established for view engines.
Removed most of the methods on the HtmlHelper class, converting them to extension methods of the HtmlHelper class instead. These methods exist in a new namespace (System.Web.Mvc.Html). If you are migrating from Preview 5, you must add the following element to the namespaces section of the Web.config file:
<add namespace="System.Web.Mvc.Html"/>
This makes it possible for you to completely replace our helper methods with your own.
Changed the default model binder (DefaultModelBinder) to handle complex types. The IModelBinder interface has also been changed to accept a single parameter of type ModelBindingContext.
Added a new HttpVerbs enumeration that contains the most commonly used HTTP verbs (GET, POST, PUT, DELETE, HEAD). Also added a constructor overload to AcceptVerbsAttribute that accepts the enumeration. The enumerated values can be combined. Because it is possible to respond to HTTP verbs that are not included in the enumeration, the AcceptVerbsAttribute retains the constructor that accepts an array of strings as a parameter. For example, the following snippet shows an action method that can respond to both POST and PUT requests.
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Put)]
public ActionResult Update() {...
}
Modified the RadioButton helper method to ensure that every overload accepts a value. Because radio buttons are used to specify a choice from a set of possible values, specifying a value for a radio button is necessary.
Made modifications and fixes to the default project template. This includes moving script files to a new Scripts folder. The default template uses the ModelState class to report validation errors.
Changed action-method selection. If two action methods match a request, but only one of those has an attribute that derives from ActionMethodSelectorAttribute that matches the request, that action is invoked. In earlier releases, this scenario resulted in an exception.
For example, the following two action methods are in the same controller:
public ActionResult Edit() {
//...
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection form) {
//...
}
In Preview 5, a POST request for the Edit action would cause an exception, because two methods match the request. In the Beta, precedence is given to the method that matches the current request via the AcceptVerb attribute. In this example, the first method will handle any non-POST requests for the Edit action.
Added an overload for the ViewDataDictionary.Eval method that accepts a format string.
Removed the ViewName property from the ViewContext class.
Added an IValueProvider interface for value providers, along with a default implementation, DefaultValueProvider. Value providers supply values that are used by the model binders when binding to a model object. The UpdateModel method of the Controller class has been updated to allow you to specify a custom value provider.
I experienced the same problem as Will and had to do similar things as him, including copying the dlls to the bin folder.
Now things are working in the internal vs.net server but are causing IIS7 to crash.
Ok, it turns out one of the major problems is that I missed the step to update the compilation assemblies in the web.config:
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
All i had to do was update the assemblies from
%ProgramFiles%\Microsoft ASP.NET\ASP.NET MVC Beta
Also get the most recent Microsoft.Web.MVC from codeplex
to update my futures assembly too.
add in 2 lines to the web.config
This one to the <assemblies> Section:
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
This one to the <namespaces> section:
<add namespace="System.Web.Mvc.Html"/>
Then i had to update all the <%using (Html.Form()) to <%using (Html.BeginForm())
On one code file i had to add the System.Web.Mvc.Html; namespace
My stuff is based on Rob Conery's MVC Storefront, so anyone using that should be able to follow the above.
Hope it helps someone out there.
Disregard this... I'm a loser - it's Microsoft ASP.net in program files... not just ASP.net
Maybe this should be a second question, but I think keeping it all in one place might help.
When running the Beta installer nothing ends up changing on my PC. I don't see the folder in the Program Files folder... no assemblies are added to the GAC... even the installer gets to the last step and then hangs for around 10 minutes or so.
I've uninstalled and reinstalled a couple times now without any luck.
Anyone having a similar problem?
The problem with AutoFac has now been resolved in Revision 454 of the AutoFac code base
http://code.google.com/p/autofac/issues/detail?id=86&can=1
Im trying to find out how the new ModelBinder works, as far as I can see it's very different, but i haven't managed to find out how it works yet..
My old looked like:
public class GuestbookEntryBinder : IModelBinder
{
#region IModelBinder Members
public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState)
{
if (modelType == typeof(GuestbookEntry))
{
return new GuestbookEntry
{
Name = controllerContext.HttpContext.Request.Form["name"] ?? "",
Website = controllerContext.HttpContext.Request.Form["website"] ?? "",
Message = controllerContext.HttpContext.Request.Form["message"] ?? "",
};
}
return null;
}
#endregion
}
The new one looks like:
#region IModelBinder Members
public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
#endregion
Any hints?
I use Autofac as my DI container. A null container exception gets thrown when trying to dispose of the container objects.
Yup, also use Autofac as DI container.
Get same issue as this guy
http://groups.google.com/group/autofac/browse_thread/thread/68aaf55581392d08
No idea if a fix is possible but cant continue until this is fixed ......
After struggling with this for most of the day, I figured I'd post my solution here. Maybe this is normal Visual Studio behavior but I never noticed it before...
On my existing project, I actually had to manually move the Beta files to the Bin folder. For whatever reason, just browsing to it with Add Reference wasn't working...
Html.TextBox - value now is object, not string.
So, hidden errors possible (not at compile time and even not at runtime), for example I've used this overloaded method earlier Html.TextBox(string name, object htmlAttributes). Now my attrs go into textbox value.
About the Autofac issue. There is a thread on the autofac discussion group about the need to update the controller factory to be compatible with the Beta release of the MVC framework
http://groups.google.com/group/autofac/browse_thread/thread/68aaf55581392d08
I hope they post a new version very very soon :-)
When I upgraded from Preview 5 to Beta I had difficulty locating the generic overloads of ActionLink. It appears that those are not included in the main release of ASP.NET MVC but are being shipping as "futures".
I found the necessary assembly (Microsoft.Web.Mvc) # http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=aspnet&ReleaseId=18459
There is a breaking change in the ViewContext constructor. It has changed from:
ViewContext(ControllerContext context, string viewName, ViewDataDictionary viewData, TempDataDictionary tempData)
to:
ViewContext(ControllerContext context, IView view, ViewDataDictionary viewData, TempDataDictionary tempData)
This broke my code because I am using MvcContrib.Services.IEmailTemplateService, which takes a ViewContext in its RenderMessage method. To get an IView from the template name, I am doing the following:
var view = ViewEngines.DefaultEngine.FindView(controllerContext, viewName, null);
Not sure if this is the best practice, but it seems to work.
This is now broken:
<%=Html.TextBox("Name", new Hash(#class => "required"))%>
In Preview 5 the above would bind the value of ViewData.Model.Name to the textbox. This still works:
<%=Html.TextBox("Name")%>
But if you want to specify html attributes, you must also specify the value as follows:
<%=Html.TextBox("Name", ViewData.Model.Name, new Hash(#class => "required"))%>
Actually this is not really safe. If there is any chance ViewData.Model might be null you need to do something like this:
<%=Html.TextBox("Name", ViewData.Model == null ? null : ViewData.Model.Name, new Hash(#class => "required"))%>
This change seems counter to the Beta release notes:
"...in order to reduce overload
ambiguity...the value parameter was changed
from object to string for several
helper methods."
The value parameter for TextBox used to be string, and it was changed to object. So to avoid ambiguities they had to remove the one overload that I use the most. :(
IMHO, every HTML helper method should have overloads that allow binding in all cases without specifying the value. Otherwise we will end up with inconsistent view code that will confuse future devs.
If you are using Html.Form from the futures assembly (Microsoft.Web.Mvc) you might get a name collision on the FormMethod enum. For example:
Html.Form<FooController>(c => c.Bar(), FormMethod.Post, new Hash(#class => "foobar"))
This will complain that FormMethod is an ambiguous reference between Microsoft.Web.Mvc and System.Web.Mvc. This is quite sad because IMHO BeginForm does not provide a viable option due to its lack of an override that uses a lambda expression. Your only option is to use magic strings, which resist refactoring.
The best solution, it seems, is to put the following into every view that uses FormMethod:
<%# Import Namespace="FormMethod=Microsoft.Web.Mvc.FormMethod"%>
Ugh. Hopefully this is temporary. I expect that the futures assembly can be changed to use the enum from System.Web.Mvc. Or much better yet, hopefully they overload BeginForm to use expressions.
It seems that Html.Image is broken. As of preview 5 it was moved to the futures assembly. I cannot imagine why. Anyway, the error is:
Method not found: 'Void System.Web.Mvc.UrlHelper..ctor(System.Web.Mvc.ViewContext)'
The best solution I can see is to replace this:
<%=Html.Image("~/Content/Images/logo.jpg") %>
with this:
<img src="<%=Html.ResolveUrl("~/Content/Images/logo_350.jpg")%>" />
What Will said above, except that in addition to deleting the assemblies from the GAC and re-adding the references I also had to run the Beta installer again (putting the right assemblies in the GAC this time, though I'm just using a file reference).
I suspect if I'd deleted the Preview 5 assemblies from the GAC (and I've no idea how they got in there either) before I ran the installer, everything might have been OK. Worth trying.
In the unlikely event that anyone else out there is as daft as me and working on Vista, you may not need to do the registry hacking above in order to delete the old assemblies - just run gacutil from an admin command prompt. Doh!
I found that updating the web.config namespaces element with the namespaces from a blank project fixed my problems. I also had to update my ModelBinders due to the interface change.

Resources