I am integrating an MVC project with my current Web Forms Application. I used Nuget to install MVC into the solution (based on some reading I did, which suggested doing this will add the binaries and configuration necessary). I then added a new MVC project into the solution and edited the Global.asax.vb file as following for MVC Routes:
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs on application startup
MVC.RouteConfig.RegisterRoutes(RouteTable.Routes)
End Sub
I configure my RouteConfig to ignore aspx files
Public Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.IgnoreRoute("{*aspx}")
routes.MapRoute(
name:="Default",
url:="{controller}/{action}/{id}",
defaults:=New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional}
)
End Sub
My Web form routes work fine but my MVC one doesn't. How can I fix this?
Error:
HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Controller:
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Return View()
End Function
Function About() As ActionResult
ViewData("Message") = "Your application description page."
Return View()
End Function
Function Contact() As ActionResult
ViewData("Message") = "Your contact page."
Return View()
End Function
End Class
Related
I am unable to post form data from an ASP.NET Web form to my Web API controller because I keep getting a 404 or 'Not found' error.
Here is the code in my Global.asax.vb file:
Imports System.Web.Optimization
Imports System.Web.Http
Public Class Global_asax
Inherits HttpApplication
Sub Application_Start(sender As Object, e As EventArgs)
' Fires when the application is started
RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
RouteTable.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", New With {.id = RouteParameter.Optional})
End Sub
End Class
This is the AJAX in the Web Form:
$.ajax({
type: 'POST',
url: 'api/UserApi',
data: $('form').serialize(),
success: function(response) {
var result = JSON.parse(response);
if (result.validationError) {
$('.jumbotron').prepend('<div class="alert alert-danger"><strong>Make sure to fill in all fields.</strong></div>');
if ($('.alert-danger').length > 1) {
$('.alert-danger').last().remove();
}
}
else if (result.error) {
$('.jumbotron').prepend('<div class="alert alert-danger"><strong>User with that email address already exists!</strong></div>');
if ($('.alert-danger').length > 1) {
$('.alert-danger').last().remove();
}
}
else {
if ($('.alert-danger').is(':visible')) {
$('.alert-danger').remove();
}
$('.jumbotron h1').remove();
$('form').replaceWith('<div class="alert alert-success"><strong>User created successfully!</strong></div>');
}
},
error: function(error) {
console.log(error);
}
});
Lastly, this is the code in the Web API controller:
Imports System.Net
Imports System.Web.Http
Imports System.Web.Http.Results
<RoutePrefix("api/UserApi")>
Public Class UserApiController
Inherits ApiController
' POST api/<controller>
Public Sub PostValue(<FromBody()> ByVal email As String, name As String)
Dim user = New User With {
.Email = email,
.Name = name
}
Dim userDbContext = New UserDBContext()
userDbContext.Users.Add(user)
End Sub
End Class
The controller is called "UserApiController.vb" and it is in the main directory of the project. I've tried putting the exact same routing in the RouteConfig.vb file in the App_Start directory as well as using the annotation for routing you see above my class declaration in my controller, and still no luck. Also, I've written the route as RouteTable.Routes.MapHttpRoute("DefaultApi", "api/{action}/{id}", New With {.controller = "UserApi", .id = RouteParameter.Optional}) and posting to the url api/PostValue in AJAX.
Make sure your controller is in the ASP.NET folder called App_Code.
Right-click website (or sim)
> Add
> Add ASP.NET Folder
> App_Code
All .cs code should be in there. (Not pages' code-behind files.)
I'm beginner on stack overflow and in ASP.NET in general but I'll try to make my point clear here.
I'm developping a Web API in VB.NET but I'm stuck when I try to define routes.
I have for example these functions :
Public Function GetAllInformations() As IEnumerable(Of cl_information)
'return all informations
End Function
Public Function GetInformations(p_id As Int16) As IHttpActionResult
'return a specific informations
End Function
Public Function PutInformation(p_information As cl_information) As IHttpActionResult
'return the http statuscode depending on the update of the information
End Function
Public Function PostInformation(p_information As cl_information) As IHttpActionResult
'return the http statuscode depending on the post of the information
End Function
When I try this controller, using postman, I firsty check the GET method for the URI : /api/informations. The GetAllInformations() method is correctly triggered.
But when I try the GET method for a specific information item, on this kind of URI : /api/informations/i , the GetAllInformations() is also triggered.
I've got these informations from the event journal in visual studio :
"data": {
"baseType": "RequestData",
"baseData": {
"ver": 2,
"id": "12785441767974844366",
"name": "GET informations [id]",
"startTime": "2016-05-12T08:56:49.4044704+02:00",
"duration": "00:00:04.1740006",
"success": true,
"responseCode": "200",
"url": "http://localhost:51651/api/informations/i",
"httpMethod": "GET",
"properties": {
"DeveloperMode": "true"
}
}
I don't know why the request is not correctly routing to my GetInformations(p_id As Int16) function. Could you help me here please ?
FYI : I have this basic routes configuration :
Public Module WebApiConfig
Public Sub Register(ByVal config As HttpConfiguration)
' Configuration et services API Web
' Itinéraires de l'API Web
config.MapHttpAttributeRoutes()
config.Routes.MapHttpRoute(
name:="DefaultApi",
routeTemplate:="api/{controller}/{id}",
defaults:=New With {.id = RouteParameter.Optional}
)
End Sub
End Module
EDIT :
I tried to implement a method to handle both cases, with an optional argument, but the parameter isn't detected, event if I test the URI : /api/informations/i
Public Function GetInformations(Optional p_id As Int16 = 0) As IHttpActionResult
If p_id = 0 Then
'return all informations
End If
'return a specific information
End Function
after a day and a half on this, my mind is blowing but I finally found the problem.
I was using a wrong parameter name :
Public Function GetInformations(p_id As Int16) As IHttpActionResult
So I changed it by :
Public Function GetInformations(id As Int16) As IHttpActionResult
and it's working.
I have vb.net web api controller that I am trying to invoke but I'm getting back the following:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:26944/api/employee/GetPerson/'.","MessageDetail":"No action was found on the controller 'Employee' that matches the request."}
This is the web controller:
Public Class EmployeeController
Inherits ApiController
Private ReadOnly dbContext As MyEntities
Sub New()
Me.dbContext = New MyEntities
End Sub
<HttpGet>
<ActionName("GetPerson")>
Function Person(ByVal missionaryId As Integer) As IPRS_Data.getPersInfoDetail_Result
Return Me.dbContext.getPersInfoDetail(missionaryId).First
End Function
End Class
WebApiConfig:
Public Shared Sub Register(ByVal config As HttpConfiguration)
' Web API configuration and services
' Web API routes
config.MapHttpAttributeRoutes()
config.Routes.MapHttpRoute(
name:="DefaultApi",
routeTemplate:="api/{controller}/{id}",
defaults:=New With {.id = RouteParameter.Optional}
)
Dim xmlFormat = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(Function(t) t.MediaType = "application/xml")
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(xmlFormat)
End Sub
I'm invoking the service using: appbase/api/employee/GetPerson/
Your method is decorated with HTTPGET and Actionname attribute. You don't need that if you have your method name starting with "Get" (like GetPerson). However, the Actionname is obsolete as it is not considered in your actual routing. Your routing is "api/{controller}/{id}". If you want your action name being considered you need to modify your routing to "api/{controller}/{action}/{id}". And if you want to have your id-param being considered per default routing you should rename the param in your method from missionaryId to just id.
Function Person(ByVal id As Integer) As IPRS_Data.getPersInfoDetail_Result
Return Me.dbContext.getPersInfoDetail(id).First
End Function
And that's the way how to invoke it (don't forget to pass an Id because there is no other "GET" method in your controller which works paramless.
appbase/api/employee/15
or
appbase/api/employee?id=15
and if you insist on missionaryId
appbase/api/employee?missionaryId=15
I'm integrating OpenID to my existing application with LiveID and Google providers. On my login page, in addition to the original login fields I have added 'Log in with Google' and 'Log in with Microsoft' buttons.
I can successfully read the AuthenticationResult data for both providers above, but am accomplishing this in the following manner...
For the new login buttons I crafted a return URL to differentiate them on the user's return:
Protected Sub btn_google_Click(sender As Object, e As EventArgs) Handles btn_google.Click
Dim client As New GoogleOpenIdClient
Dim u As New System.Uri("http://www.mytest.com/login.aspx?action=signin&provider=google")
client.RequestAuthentication(New HttpContextWrapper(HttpContext.Current), u)
End Sub
Protected Sub btn_live_Click(sender As Object, e As EventArgs) Handles btn_live.Click
Dim client As New MicrosoftClient("xyz", "12345")
Dim u As New System.Uri("http://www.mytest.com/login.aspx?action=signin&provider=microsoft")
client.RequestAuthentication(New HttpContextWrapper(HttpContext.Current), u)
End Sub
So when the user gets redirected back to login.aspx, I then have the following checks to process the login functionality:
If Not Page.IsPostBack Then
If Request.QueryString("action") IsNot Nothing AndAlso Request.QueryString("action").Trim = "signin" Then
If Request.QueryString("provider") IsNot Nothing AndAlso Request.QueryString("provider").Trim <> String.Empty Then
Select Case Request.QueryString("provider").Trim
Case "microsoft"
Dim client As New MicrosoftClient("xyz", "12345")
Dim u As New System.Uri("http://www.mytest.com/loginlive.aspx?action=signin&provider=microsoft")
Dim result As DotNetOpenAuth.AspNet.AuthenticationResult = client.VerifyAuthentication(New HttpContextWrapper(HttpContext.Current), u)
' remainder of logic removed
' ...
Case "google"
Dim client As New GoogleOpenIdClient
Dim result As DotNetOpenAuth.AspNet.AuthenticationResult = client.VerifyAuthentication(New HttpContextWrapper(HttpContext.Current))
' remainder of logic removed
' ...
End Select
End
End
End If
My main question here is, is this a good way to process AuthenticationResults? Or, is there a better/more secure/more clever way to accomplish the same?
Better way would be to use Abstract Factory pattern in combination with Command Pattern. Which can reduce the hard coding and also have the code loosely coupled, so you can extend the functionality in future for each of the authentication provider. Find the snippet of each section of the code below
Abstract Class for "BaseAuthentication Provider"
public abstract class BaseAuthenticationProvider
{
//abstract Methods that need to be invoked from the concrete class, this need to be decided based on the functionality you need to achieve. This function would be invoked using the command pattern.
// AuthorizeUser() : this method would be invoked to authorize the user from the provider
//AuthenticateUser() : this method would be invoked once the user is redirected from the provider site.
//abstract Properties that will hold the base information for the authentication provider, this need to be decided based on the functionality you need to achieve
//CustomerSecret
//CustomerConsumerKey
}
Use the following code snippet to implement concrete class for the Gooogle, Yahoo, Microsoft etc.
public class GoogleAuthentication : BaseAuthenticationProvider
{
public GoogleAuthentication()
{
//initialization
}
public void AuthorizeUser()
{
//code
}
public string CustomerSecret()
{
//code
}
public string CustomerConsumerKey()
{
//code
}
}
Factory class to create the concrete object, to prevent from creating instance of this factory class implement a private constructor.
public class AuthenticationProviderFactory
{
private AuthenticationProviderFactory()
{
}
public static BaseAuthenticationProvider GetInstance(string Domain)
{
switch (Domain)
{
case "google":
return new GoogleAuthentication();
case "yahoo":
return new YahooAuthentication();
}
}
}
Login.aspx : have buttons for each of the authentication provider, set the value for "CommandName" for each of the button and link all the buttons to the same event handler
for e.g. btn_google.CommandName = "google"
Protected Sub AuthenticationProvider_Click(sender As Object, e As EventArgs) Handles btn_google.Click, btn_yahoo.Click
AuthenticationProviderFactory.GetInstance(((Button)sender).CommandName).AuthorizeUser();
End Sub
Respective AuthorizeUser method would call the respective provider site for authentication. When provider redirects the user to the return URL, apply the same pattern on the Page_Load event and call the Autheticate Method from the abstract class.
Ok so I have this interesting ASP.NET MVC 4 solution/project structure, which creates pluggable application modules. I created it following this technique:
http://geekswithblogs.net/cokobware/archive/2013/01/15/asp.net-mvc-4-pluggable-application-modules.aspx
As a result, I have a main application with an empty Areas folder in the project. I also have a Plugin project that resides in the Areas folder of the main application on disk, and it also sets its build output folder to the main application \bin folder.
In my pluggable module application, I decided to create an Areas section within it, and created an Area called Test. By default, the ASP.NET MVC 4 view engine doesn't support it as a pluggable module because it tries to look for the View in the incorrect location.
So conceptually, we have:
Main <- Main application folder
Areas <- Main application folder
Plugin <- Plugin module application folder
Areas <- Plugin module application folder
Test <- Plugin module application folder
To fix this, I created a way to interpret the AreaName property in a customized RazorViewEngine class to rewrite the URL the view engine is looking for to find the view files in these pluggable module areas.
First, I use the following convention to define my Area registration class for the Test Area belonging to my pluggable modules:
Namespace Areas.Plugin
Public Class PluginAreaRegistration
Inherits AreaRegistration
Public Overrides ReadOnly Property AreaName() As String
Get
Return "Plugin.Test"
End Get
End Property
Public Overrides Sub RegisterArea(ByVal context As System.Web.Mvc.AreaRegistrationContext)
context.MapRoute( _
"Plugin_default", _
"Plugin/Test/{controller}/{action}/{id}", _
New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional},
{"Plugin.Test.Controllers"}
)
End Sub
End Class
End Namespace
I then inherited the the RazorViewEngine and overrode some methods to parse and generate the views path in the pluggable module's Areas folder:
Public Class MyExtendedRazorViewEngine
Inherits RazorViewEngine
' set the location format strings
Public Sub New()
MyBase.PartialViewLocationFormats = _
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml",
"~/Areas/{3}/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{3}/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{1}/Views/Shared/{0}.cshtml",
"~/Areas/{1}/Views/Shared/{0}.vbhtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.cshtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml"
}
MyBase.AreaViewLocationFormats = {
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.cshtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml"
}
MyBase.AreaMasterLocationFormats = {
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.cshtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml"
}
MyBase.AreaPartialViewLocationFormats = {
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.cshtml",
"~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml"
}
MyBase.ViewLocationFormats = {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
}
MyBase.MasterLocationFormats = {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
}
MyBase.PartialViewLocationFormats = {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
}
End Sub
Protected Overrides Function CreatePartialView(controllerContext As ControllerContext, partialPath As String) As IView
Dim area As String = controllerContext.RouteData.DataTokens.Item("Area")
Dim areaname As String()
Dim pp As String = partialPath
If Not area Is Nothing Then
areaname = area.Split(".")
If areaname.Length > 1 Then
pp = pp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
End If
End If
Return MyBase.CreatePartialView(controllerContext, pp)
End Function
Protected Overrides Function CreateView(controllerContext As ControllerContext, viewPath As String, masterPath As String) As IView
Dim area As String = controllerContext.RouteData.DataTokens.Item("Area")
Dim areaname As String()
Dim vp As String = viewPath
Dim mp As String = masterPath
If Not area Is Nothing Then
areaname = area.Split(".")
If areaname.Length > 1 Then
vp = vp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
mp = mp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
End If
End If
Return MyBase.CreateView(controllerContext, vp, mp)
End Function
Protected Overrides Function FileExists(controllerContext As ControllerContext, virtualPath As String) As Boolean
Dim area As String = controllerContext.RouteData.DataTokens.Item("Area")
Dim areaname As String()
Dim vp As String = virtualPath
If Not area Is Nothing Then
areaname = area.Split(".")
If areaname.Length > 1 Then
vp = vp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
End If
End If
Return MyBase.FileExists(controllerContext, vp)
End Function
End Class
I've modified the main application Global.asax file to pick up the new view engine:
Imports System.Web.Http
Imports System.Web.Optimization
Public Class MvcApplication
Inherits System.Web.HttpApplication
Sub Application_Start()
AreaRegistration.RegisterAllAreas()
WebApiConfig.Register(GlobalConfiguration.Configuration)
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
ViewEngines.Engines.Clear()
ViewEngines.Engines.Add(New MyExtendedRazorViewEngine())
End Sub
End Class
After launching the browser and invoking the Home controller for my main application, I see the correct pages and layout render. When I go to the Home controller action for the Index for my Plugin module, again the Index view renders properly with the _Layout.vbhtml being picked up from the main application.
However, when I invoke the Home controller action for the Index view of Plugin's Test Area, I can only see the Index page view render, but the master _Layout.vbhtml isn't being included from the main application.
What am I missing to get the Areas views below the Plugin pluggable module to render the main application's master layout template?